-------The Wizard of Id's WizMath------ A 4am crack 2020-06-18 --------------------------------------- Name: The Wizard of Id's WizMath Genre: educational Year: 1984 Credits: Chuck Benton Publisher: Sierra On-Line Platform: Apple ][+ or later (48K) Media: 5.25-inch disk Sides: 1 OS: DOS 3.3 Previous cracks: none ~ Chapter 0 In Which Various Automated Tools Fail In Interesting Ways COPYA no errors, but crashes when starting a game Locksmith Fast Disk Backup ditto EDD 4 bit copy (no sync, no count) ditto Passport copies but applies no patches copy has same behavior has others Copy ][+ nibble editor no obvious shenanigans no track $23 Disk Fixer standard DOS 3.3 T11 is standard DOS 3.3 disk catalog T01,S09 -> startup program is "HELLO" Why didn't COPYA work? Probably a runtime protection check looking for... something? Between the sectors I guess? Why didn't Locksmith FDB work? ditto Why didn't my EDD copy work? An excellent question... that I can't yet answer. Next steps: 1. Find the protection check 2. Disable it 3. Declare victory (*) (*) but do not go to the gym until there's a vaccine ~ Chapter 1 Unlucky In Cards Since my copy goes down a different code path than the original, I'm guessing there is a runtime protection check somewhere. One thing that all 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 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 ------------- $00/$07-$4F $0B/$01-$73 --^-- The match on track 0 is part of DOS 3.3 and is legitimate. Let's see what lurks on track $0B. According to Copy ][+ track/sector map, T0B,S01 is part of a file named "OBJ.8800" which -- and I'm just making an educated guess here -- is probably loaded at $8800. Following the file and counting sectors, it appears that the sector in question ends up at $9600. Thanks to the 4-byte offset in DOS 3.3 files, offset $73 will end up being loaded at $966F. ]PR#6 ...... No reset or keyboard trapping, so a well-timed gets me a working BASIC prompt with DOS in memory. ]LIST 10 HOME 15 PRINT "MAXFILES 1" 20 PRINT "BLOAD PICTURES" 30 PRINT "BLOAD EMULATE.TBL" 40 PRINT "BLOAD DRAW.OBJ" 48 PRINT "BLOAD OBJ.8800,A$8800 " 49 PRINT "BLOAD OBJ.7000,A$7000 " 50 PRINT "BLOAD NAMES" 60 PRINT "BRUN OBJ.0800" ]CATALOG DISK VOLUME 254 A 006 HELLO B 026 OBJ.0800 B 026 OBJ.7000 B 020 OBJ.8800 B 013 EMULATE.TBL B 003 DRAW.OBJ B 093 PICTURES B 034 SPOOK B 009 SPKESC0 B 009 SPKESC1 B 034 KING B 006 NAMES ]MAXFILES 1 ]BLOAD OBJ.8800,A$8800 ]CALL -151 *966FL 966F- BD 89 C0 LDA $C089,X Bingo. ~ Chapter 2 Are You Watching Closely? It appears the routine itself starts at $9668. Immediately before that is a JMP instruction, and there don't seem to be any branches to $9668. *9668L 9668- A9 00 LDA #$00 966A- AA TAX 966B- A8 TAY ; seek to track 0 966C- 20 52 B0 JSR $B052 ; turn on drive motor 966F- BD 89 C0 LDA $C089,X ; maybe a Death Counter? 9672- A9 05 LDA #$05 9674- 8D 00 BB STA $BB00 9677- 20 AB 96 JSR $96AB *96ABL 96AB- A9 1C LDA #$1C 96AD- 8D 02 BB STA $BB02 ; reset data latch 96B0- BD 8E C0 LDA $C08E,X ; find $D5 nibble 96B3- BD 8C C0 LDA $C08C,X 96B6- 10 FB BPL $96B3 96B8- C9 D5 CMP #$D5 96BA- EA NOP 96BB- EA NOP 96BC- F0 0F BEQ $96CD ; $BB01/$BB02 is a failsafe counter for ; finding the nibble. (Note that $BB01 ; is uninitialized, but it's the low ; byte of the 2-byte word so it doesn't ; matter much.) 96BE- CE 01 BB DEC $BB01 96C1- D0 F0 BNE $96B3 96C3- CE 02 BB DEC $BB02 96C6- D0 EB BNE $96B3 ; if we can't even find $D5, pop the ; stack (because we JSR'd here) and ; jump to what I assume is The Badlands 96C8- 68 PLA 96C9- 68 PLA 96CA- 4C 9B 96 JMP $969B ; execution continues here (from $96BC) ; once we find the $D5 nibble 96CD- BD 8C C0 LDA $C08C,X 96D0- 10 FB BPL $96CD ; now looking for $AA 96D2- C9 AA CMP #$AA 96D4- D0 E2 BNE $96B8 96D6- 48 PHA 96D7- 68 PLA 96D8- BD 8C C0 LDA $C08C,X 96DB- 10 FB BPL $96D8 ; then $96 96DD- C9 96 CMP #$96 96DF- D0 F1 BNE $96D2 96E1- A0 05 LDY #$05 96E3- 20 10 97 JSR $9710 *9710L ; a compact little subroutine that will ; skip over a given number of nibbles ; (Y is set in the caller) 9710- BD 8C C0 LDA $C08C,X 9713- 10 FB BPL $9710 ; just burning cycles so we don't ; accidentally read the same nibble ; twice 9715- 48 PHA 9716- 68 PLA 9717- 88 DEY 9718- D0 F6 BNE $9710 ; note that the last nibble read is ; still in A when we return 971A- 60 RTS Continuing from $96E6... ; the 5th nibble needs to be $AA (this ; will be part of the sector number in ; the address field) 96E6- C9 AA CMP #$AA 96E8- D0 C9 BNE $96B3 96EA- BD 8C C0 LDA $C08C,X 96ED- 10 FB BPL $96EA ; this will be the other half of the ; sector number in the address field ; (it's 4-and-4 encoded, $AA $AA -> 0) 96EF- C9 AA CMP #$AA 96F1- D0 C0 BNE $96B3 96F3- 48 PHA 96F4- 68 PLA ; unconditionally (without counting) ; look for the next $D5 nibble, which ; should be the start of the data ; prologue for sector 0 96F5- BD 8C C0 LDA $C08C,X 96F8- 10 FB BPL $96F5 96FA- C9 D5 CMP #$D5 96FC- D0 F7 BNE $96F5 96FE- EA NOP 96FF- BD 8C C0 LDA $C08C,X 9702- 10 FB BPL $96FF ; $AA nibble, also part of the data ; prologue 9704- C9 AA CMP #$AA 9706- D0 F2 BNE $96FA 9708- EA NOP ; skip $100 nibbles 9709- A0 00 LDY #$00 970B- 20 10 97 JSR $9710 ; skip $5B more nibbles 970E- A0 5B LDY #$5B ; note: this falls through to the same ; subroutine entry point we called ; earlier, which is nice 9710- BD 8C C0 LDA $C08C,X 9713- 10 FB BPL $9710 9715- 48 PHA 9716- 68 PLA 9717- 88 DEY 9718- D0 F6 BNE $9710 971A- 60 RTS So far, we've seeked to track 0, found sector 0, and skipped basically all of it. Continuing from $967A... ; this will always branch, because the ; last thing that happened was that Y ; was decremented to 0, which is still ; technically positive as far as the ; 6502 is concerned 967A- 10 01 BPL $967D ; and this is all bogus 967C- 20 C8 C0 JSR $C0C8 967F- 30 5D BMI $96DE 9681- 8C C0 90 STY $90C0 9684- F8 SED Are you watching closely? We took an (unconditional) branch into the middle of an instruction, from $967A to $967D, but the monitor is showing a listing of $967C and beyond. None of that is real; it will never be executed. Execution continues at $967D. *967DL ; burn some number of cycles ; (Y starts at 0 because that's where ; it ended in the previous subroutine) 967D- C8 INY 967E- C0 30 CPY #$30 ; maybe keeping a rolling checksum of ; raw data latch values?!? 9680- 5D 8C C0 EOR $C08C,X ; EOR does not affect the carry bit, ; so this branches based on the CPY, ; not any of the data latch values 9683- 90 F8 BCC $967D ; OK not a rolling checksum, because ; now we're discarding it and reading ; the data latch again 9685- BD 8C C0 LDA $C08C,X ; on first glance this looks like a ; regular LDA/BPL loop to read a nibble ; EXECPT it's branching forwards, not ; backwards 9688- 10 0A BPL $9694 ; now comparing the RAW DATA LATCH READ ; at $9685 968A- C9 C9 CMP #$C9 968C- D0 0D BNE $969B ; success path falls through here -- ; turn off drive motor and return the ; Death Counter value to the caller 968E- BD 88 C0 LDA $C088,X 9691- 4C A7 96 JMP $96A7 ... 96A7- AD 00 BB LDA $BB00 96AA- 60 RTS ; execution continues here (from $9688) ; if the raw data latch read is NOT the ; expected $C9 value 9694- EA NOP 9695- EA NOP ; apparently we get one more chance to ; find the right nibble, because after ; burning some cycles with the NOPs, if ; the data latch has gone high (meaning ; a full nibble value is available), we ; branch back to the CMP to see if it ; now matches the expected $C9 nibble 9696- BD 8C C0 LDA $C08C,X 9699- 30 EF BMI $968A ; decrement Death Counter and try again 969B- CE 00 BB DEC $BB00 969E- D0 D7 BNE $9677 ; out of chances -- corrupt the stack ; (two pushes but only one pull) and ; jump to what I previously called the ; "success" path, except now that the ; stack is corrupted, it will crash ; when it tries to return to the caller 96A0- 48 PHA 96A1- 98 TYA 96A2- 48 PHA 96A3- 68 PLA 96A4- 4C 8E 96 JMP $968E So that's it. At one of two exact times after sector 0, there needs to be a $C9 nibble. If we find it, we gracefully exit; otherwise, we corrupt the stack and crash. So why can't EDD copy it? ~ Chapter 3 Now You See It, Now You Don't Using the Copy ][+ nibble editor, we can look at the raw nibbles on track 0, including the crucial $C9 nibble. Here is track 0, starting near the end of the data field of sector 0. (I've converted the inverse characters into "+" signs to indicate where Copy ][+ found one or more timing bits after a nibble.) --v-- COPY ][ PLUS BIT COPY PROGRAM 8.4 (C) 1982-9 CENTRAL POINT SOFTWARE, INC. ---------------------------------------- TRACK: 00 START: 2029 LENGTH: 188E 2188: 96 96 96 96 96 96 96 96 VIEW 2190: 96 96 96 96 96 96 96 EA 2198: EC 9A DE AA EB EA+FF+FF+ 21A0: FF+FF+BA+FF+FF+FF+FF+FF+ 21A8: FF+FF+FF+FE+C9+FF+FF+FF+ <-21AC 21B0: FF+FF+FF+AC+FF+99+FF+99+ 21B8: E4 E4 E4+FF+A4 FF+9C FF+ 21C0: FF+FF+FF+FF+92 92 92 FF+ 21C8: 96 FF+FF+FF+9D F2+FF+FF+ --^-- The exact nibble offsets are not important (they'll change if we re-read the disk), but this is what we have at offset $219A: DE AA EB ; data epilogue EA FF FF FF FF BA FF FF FF FF FF FF FF FF FE C9 ... Seems normal enough. But let's read the same track again... --v-- 26F0: 96 96 96 96 96 96 96 96 VIEW 26F8: 96 96 96 96 96 EA EC 9A 2700: DE AA EB EA+D9+FF+E5 E5+ 2708: FF+FF+FF+FF+FF+9F E7 F9 2710: FC C9 C9 C9+FF+FF+FF+D6 <-2712 2718: DB+FF+A4+FF+FF+FF+AD 94 2720: FF+FF+A9 A9 CB+FF+FF+FF+ 2728: FF+FF+FF+EA FF+FF+FF+FF+ 2730: FF+BA+FF+FF+FF+FF+FF+FF+ --^-- Again, the exact nibble offsets are not important. But look at the "data" after sector 0 (starting at offset $2700): DE AA EB ; data epilogue EA D9 FF E5 E5 FF FF FF FF FF 9F E7 F9 FC C9 C9 C9 In fact, this region of the original disk will look different every time we read it. How? I'm glad you asked. There's only one thing you can put on a disk that will change every time you read it: nothing. And by "nothing," I mean "a long sequence of zero bits." And that's what is on the original disk before (and, as it turns out, after) the $C9 nibble: nothing. A bit of background. When we say a "zero bit," we really mean "the lack of a magnetic state change." The Disk II drive isn't digital; it's analog. If it doesn't see a state change in a certain period of time, it calls that a "0". If it does see a change, it calls that a "1". But the drive can only tolerate a lack of state changes for so long -- about as long as it takes for two bits to go by. Fun fact(*): this is why disks use nibbles as an intermediate on-disk format in the first place. No valid nibble contains more than two zero bits consecutively, when written from most- significant to least-significant bit. (*) not guaranteed, actual fun may vary So what happens when a drive doesn't see a state change after the equivalent of two consecutive zero bits? The drive thinks the disk is weak, and the MC3470 chip inside the drive starts increasing the amplification to try to compensate, looking for a valid signal. But there is no signal. There is no data. There is just a yawning abyss of nothingness. Eventually, the drive gets desperate and amplifies so much that it starts returning random bits based on ambient noise from the disk motor and the magnetism of the Earth. Seriously. It's trivial to write zero bits to a disk; just write a #$00 nibble to write 8 zero bits -- like any other 8-bit nibble. You can write whatever you want to a disk; it doesn't need to be what DOS would consider a "valid" nibble. But when you read that nibble back, the drive can't handle 8 zero bits in a row, so it will actually return some random bits. Which is why no one does that. Returning random bits doesn't sound very useful for a storage device, but it's exactly what the developer wanted, and that's exactly what this copy protection scheme depends on. Here's why: Bit copiers can't duplicate a long sequence of zero bits. Why? Because that's not what they see. What they see is some random bits -- the real zero bits interspersed with phantom "1" bits. So that's what they write to the target disk. Whatever randomness they get when they read the original disk will essentially get "frozen" onto the copy. But wait, it gets worse. ~ Chapter 4 In Which We Jump Out Of The System, Then Jump Back In To understand exactly what is on the original disk, and why this copy protection works, we need to jump outside the system and use modern tools that can see the disk at a different level. With an Applesauce hardware controller, a modified drive, and the associated Applesauce.app software, we can get a flux-level read of exactly what is on the original disk. Starting immediately after the data epilogue of sector 0: 11101010 ; $EA nibble 00000000 ; long sequence of 0 bits ... ; (99 "bits" long) 1111111100 ; $FF/10 (sync nibble) 1111111100 ; $FF/10 (sync nibble) 111111100 ; $FE/9 (sync nibble) 11001001 ; $C9 nibble 00000000 ; long sequence of 0 bits ... ; (1141 "bits" long) Those 99 bits after the $EA nibble can randomly resolve into anything, which will then be interpreted as nibbles. The sync nibbles might resynchronize the stream, so that when it reads the data latch at the crucial moment, the $C9 nibble is there. Or they might not. There aren't enough sync nibbles to guarantee resynchronization. (See p. 3-8 of "Beneath Apple DOS" for a more detailed explanation of why you need at least four $FF/10 nibbles to resynchronize in all cases.) With the element of randomness, it's possible that those 99 zero bits could resolve to a bitstream where the nibbles are still out of phase at the crucial moment when it checks for the $C9 nibble. In that case, it will decrement the Death Counter in $BB00 and start over. It has 5 chances to get it right. Could this actually happen? I have the original disk and real hardware, so let's find out. Since the protection routine is self-initializing, self- contained, unencrypted, and returns the value of the Death Counter, I can non- destructively test my original disk by loading the routine and calling it repeatedly, and looking at the return value. [S6,D1=original disk] ]PR#6 ... ]MAXFILES 1 ]BLOAD OBJ.8800,A$8800 ]CALL -151 *300L 0300- 20 68 96 JSR $9668 0303- AA TAX 0304- FE 40 03 INC $0340,X 0307- A9 AE LDA #$AE 0309- 20 F0 FD JSR $FDF0 030C- CE 40 03 DEC $0340 030F- D0 EF BNE $0300 0311- 60 RTS *340:0 0 0 0 0 0 *300G ... *341.345 0341- 02 08 0C 29 C1 That's a nice distribution. About 90% of the time, it succeeds on the first or second try. But it clearly shows that the Death Counter is a necessary component of this copy protection. Two times out of 256, it only succeeded on the last try! I ran the same test a few more times and got a similar distribution. But on the fourth run, it crashed, confirming that -- about once every 1000 runs -- this protection check can and will fail on an original disk. ~ Chapter 5 May The Odds Be Ever In Your Favor So what happens on a bit copy? The short answer is... it depends. It might work! It's random! Maybe it will interpret the random bitstream as a sequence of nibbles that is just the right length AND just happens to synchronize to the $C9 nibble, in which case the copy will pass the protection check. The long answer is... the deck is stacked against you. In the general case, bit copiers are trying to do the impossible, and they succeed a surprising amount of the time. On a disk like this, most parts of each track are highly structured. There are sectors. There are address fields and data fields, each with prologues and epilogues and internal checksums. By design, you can verify that you read each of these things properly. But the bits between the sectors are the "wild west," with little to no structure to fall back on. Compounding their troubles, bit copiers can not tell the difference between 1 and 2 zero bits after a nibble. The disk is just too fast and the CPU is too slow; there's barely enough time to determine that there's a single zero bit (and even that routine has limitations that can be exploited). Sync nibbles ($FF/10) are generally OK because they can assume that those are being used as sync nibbles. But this is an assumption, not anything they can verify in real time, and after 4 sync nibbles in a row, even advanced bit copiers like EDD will start writing out $FF/9 instead, unless you tell them otherwise. (Adventure International uses this assumption against copiers by having a long field of $FF/10 nibbles and noticing that they get rewritten as $FF/9 nibbles. See 4am crack no. 1563 "The Kingdom of Facts" for details.) Anyway, bit copiers have a fundamental problem: they can't tell exactly what's on the disk, because the disk is too fast and the CPU is too slow. So they compensate, guess, and reconstruct. They build the most probable bitstream based on the nibble stream at any given point on the track. But they do not, and can not, "duplicate" the bitstream. Every copy is a reconstruction. When you read complete garbage that changes every time it comes around, then try to reconstruct that into some sort of reasonable bitstream that makes sense as a nibble stream, you are going to fail because there aren't really any nibbles. There is just the yawning abyss. Using the same Applesauce software, I imaged my non-working copy and examined the problematic region after track 0, sector 0. Keep in mind that each copy would look slightly different, due to how it reads the random garbage. But even a single copy is instructive. 111010100 ; $EA/9 110000000 ; $C0/9 101110010 ; $B9/9 100111000 ; $9C/9 110100100 ; $D2/9 100000100 ; $82/9 10100111 ; $A7 10100111 ; $A7 101001010 ; $A5/9 100000000 ; $80/9 101010000 ; $A8/9 10111111 ; $BF 11001111 ; $CF 11110011 ; $F3 11111001 ; $F9 10010010 ; $92 100001100 ; $86/9 111010010 ; $E9 This is EXTREMELY interesting. At first glance, it looks nothing like the original disk, because some of those 99 0 bits turned into 1 bits. Fine, we knew that would happen. But more importantly, there's no $C9 nibble. Or is there? If you take the $BF nibble and ignore the first two bits, you get this: 111111 11001111 11110011 11111001 10010010 Here, let me rewrite that for you: 1111111100 ; $FF/10 1111111100 ; $FF/10 111111100 ; $FE/9 110010010 ; $C9/9 So there is a $C9 nibble, but it's out of phase, and the sync field isn't long enough to resync the data latch to see it. But more importantly, IT'S IN THE WRONG PLACE. Count the bits. Between the $EA nibble and the first $FF/10 nibble, the original disk has 99 bits. This copy only has 91 bits. By the time we try to read the crucial $C9 nibble, we're long past it. Finally, because the rest of the abyss has been turned into "real" nibbles, re-reading the track will not change the result. The bitstream is too short, the crucial nibble is out of phase, there's no longer any meaningful randomness, and you're all out of luck. The original disk might fail 0.1% of the time, but this copy will fail 100% of the time. ~ Chapter 6 Back To The Crack Searching the disk for the bytes "20 68 96" (JSR $9668, the entry point of the protection routine), I find one match on track $17. ; change "JSR" to "BIT" T17,S08,$D1: 20 -> 2C The code immediately following does not even check the return value (the Death Counter), and there do not appear to be any other side effects or delayed anti- tamper checks. ~ Epilogue Out of curiosity, I found a scan of an old EDD program list, which gives brief descriptions of programs that require non-default parameters. This game was not listed, but Sierra reused the same protection on other disks, including "King's Quest." Here is what they suggest to make a protected backup: --v-- drive speed critical recopy t0 until boots t0 parm normal or bitcopy and/or autonc or manualnc --^-- "recopy t0 until boots" Now you know why. Quod erat liberandum. --------------------------------------- A 4am crack No. 2203 ------------------EOF------------------