---------------Destroyer--------------- A 4am crack 2015-08-25 --------------------------------------- Name: Destroyer Genre: strategy Year: 1986 Authors: Michael Kosaka (design and graphics) Chuck Sommerville (programming) Publisher: Epyx Media: single-sided 5.25-inch floppy OS: custom with DOS 3.3 bootloader Previous cracks: The Scanner / Coast to Coast The Blade ~ Chapter 0 In Which Various Automated Tools Fail In Interesting Ways COPYA immediate disk read error, but it gets a participation medal just for showing up Locksmith Fast Disk Backup unable to read any track EDD 4 bit copy (no sync, no count) no errors, but the copy just reboots endlessly Copy ][+ nibble editor all tracks use standard prologues (address: D5 AA 96, data: D5 AA AD) but modified epilogues (address: FF FF FF, data: FF FF FF) Disk Fixer ["O" -> "Input/Output Control"] set Address Epilogue to "FF FF FF" set Data Epilogue to "FF FF FF" Success! All tracks readable! T00,S00 -> looks like a DOS 3.3 boot0 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 [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 /!\ BOOT0 JUMPS TO $BB00 CAPTURING BOOT1 ...reboots slot 6... ...reboots slot 5.. SAVING BOOT1 SAVING RWTS /!\ NIBBLE CHECK AT $BB00 Hey, do you think maybe there's a nibble check at $BB00? I think there might be a nibble check at $BB00. I should probably take a look at $BB00. Later. ]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 --^-- [S6,D1=demuffin'd copy] ]PR#6 ...reboots endlessly... Let's go find that nibble check. I'm gonna take a wild guess and say we should start by looking at $BB00. ~ Chapter 2 In Which We Are Not Surprised My AUTOTRACE script helpfully scanned the page at $BB00 for the hex sequence "BD 89 C0" ("LDA $C089,X"), which is a common way to turn on the drive motor manually. Technically there are other ways to do that, and depending on when the code is called, it might not even be necessary (since the drive motor is already on). But it's common enough that so I automated scanning for it. And behold! (On a normal DOS 3.3 disk, there isn't any code at $BB00. That page is used for scratch space during every sector read, so it's overwritten very early in the boot process. So any code there is already suspicious, but code that fiddles with the drive at a low level is doubly suspicious.) ]PR#5 ... ]BLOAD BOOT1,A$2600 ]CALL -151 *FE89G FE93G ; disconnect DOS *B600<2600.2FFFM ; move RWTS into place *BB00L ; clear a few bytes of zero page BB00- A9 00 LDA #$00 BB02- A2 F0 LDX #$F0 BB04- 9A TXS BB05- 95 00 STA $00,X BB07- E8 INX BB08- D0 FB BNE $BB05 BB0A- A9 0A LDA #$0A BB0C- 85 FC STA $FC ; turn on drive motor manually (aha!) BB0E- A6 2B LDX $2B BB10- BD 89 C0 LDA $C089,X BB13- BD 8E C0 LDA $C08E,X ; initialize Death Counter BB16- A9 80 LDA #$80 BB18- 85 FD STA $FD BB1A- C6 FD DEC $FD ; if Death Counter hits 0, jump to ; The Badlands (the point of no return) BB1C- F0 6E BEQ $BB8C ; this subroutine gets the next address ; field (like $B944 under DOS 3.3) BB1E- 20 96 BB JSR $BB96 ; if that failed for some reason, jump ; to The Badlands BB21- B0 69 BCS $BB8C ; that subroutine parsed the address ; field and put the sector number in ; zp$F9 BB23- A5 F9 LDA $F9 ; is it the sector we wanted? BB25- C9 05 CMP #$05 ; nope, loop and try again BB27- D0 F1 BNE $BB1A ; look for a $D5 nibble BB29- A0 00 LDY #$00 BB2B- BD 8C C0 LDA $C08C,X BB2E- 10 FB BPL $BB2B BB30- 88 DEY ; if we don't find one in time, jump to ; The Badlands BB31- F0 59 BEQ $BB8C BB33- C9 D5 CMP #$D5 BB35- D0 F4 BNE $BB2B ; look for an $E7 nibble BB37- A0 00 LDY #$00 BB39- BD 8C C0 LDA $C08C,X BB3C- 10 FB BPL $BB39 BB3E- 88 DEY ; if we don't find one in time, jump to ; The Badlands BB3F- F0 4B BEQ $BB8C BB41- C9 E7 CMP #$E7 BB43- D0 F4 BNE $BB39 ; find 2 more $E7 nibbles BB45- BD 8C C0 LDA $C08C,X BB48- 10 FB BPL $BB45 BB4A- C9 E7 CMP #$E7 ; unexpected nibble? The Badlands! BB4C- D0 3E BNE $BB8C BB4E- BD 8C C0 LDA $C08C,X BB51- 10 FB BPL $BB4E BB53- C9 E7 CMP #$E7 ; unexpected nibble? The Badlands! BB55- D0 35 BNE $BB8C ; kill some time to get out of sync ; with the "proper" start of nibbles ; (see below) BB57- BD 8D C0 LDA $C08D,X BB5A- A0 10 LDY #$10 BB5C- 24 80 BIT $80 A short digression here into some super low-level disk stuff, because this wasn't low-level enough already... $E7 $E7 $E7 $E7. What would that nibble sequence look like on disk? The answer is, "It depends." $E7 in hexadecimal is 11100111 in binary, so here is the simplest possible answer: |--E7--||--E7--||--E7--||--E7--| 11100111111001111110011111100111 But wait. Every nibble read from disk must have its high bit set. In theory, you could insert one or two "0" bits after any of those nibbles. (Two is the maximum, due to hardware limitations.) These extra "0" bits would be swallowed by the standard "wait for data latch to have its high bit set" loop, which you see over and over in any RWTS code: :1 LDA $C08C,X BPL :1 Now consider the following bitstream: |--E7--| |--E7--| |--E7--||--E7--| 11100111011100111001110011111100111 ^ ^^ (extra) (extra) The first $E7 has one extra "0" bit after it, and the second $E7 has two extra "0" bits after it. Totally legal, works on any Apple II computer and any floppy drive. A "LDA $C08C,X; BPL" loop would still interpret this bitstream as a sequence of four $E7 nibbles. Each of the extra "0" bits appear after we've just read a nibble and we're waiting for the high bit to be set again. Now, what if we miss the first few bits of this bitstream, then start looking? The disk is always spinning, whether we're reading from it or not. If we waste too much time doing something other than reading, we'll literally miss some bits as the disk spins by. This is why the timing of low-level RWTS code is so critical. Let's say we waste 12 CPU cycles before we start reading this bitstream. Each bit takes 4 CPU cycles to go by, so after 12 cycles, we would have missed the first 3 bits (marked with an X). (normal start) |--E7--| |--E7--| |--E7--||--E7--| 11100111011100111001110011111100111 XXX |--EE--| |--E7--| |--FC--| (delayed start) Ah! It's interpreted as a completely different nibble sequence if you delay just a few CPU cycles before you start reading. Also note that some of those "extra" bits are no longer being ignored; now they're being interpreted as data, as part of the nibbles that are being returned to the higher level code. Meanwhile, other bits that were part of the $E7 nibbles are now being swallowed. Now, let's go back to the first stream, which had no extra bits between the nibbles, and see what happens when we waste those same 12 CPU cycles. (normal start) |--E7--||--E7--||--E7--||--E7--| 11100111111001111110011111100111 XXX |--FC--||--FC--||--FC--| (delayed start) After skipping the first three bits, the stream is interpreted as a series of $FC $FC $FC repeating endlessly -- not $EE $E7 $FC like the other stream. Here's the kicker: generic bit copiers didn't preserve these extra "0" bits between nibbles. By "desynchronizing" (wasting just the right number of CPU cycles at just the right time), then interpreting the bits on the disk in mid-stream, developers could determine at runtime whether you had an original disk. Which is precisely the code we just saw. Here is the complete "E7 bitstream," annotated to show both the synchronized and desynchronized nibble sequences. |--E7--| |--E7--| |--E7--||--E7--| 111001110111001110011100111111001110 XXX |--EE--| |--E7--| |--FC--||--E |--E7--| |--E7--||--E7--| |--E7--| 111001110011100111111001110111001110 E--| |--E7--| |--FC--||--EE--| |--E |--E7--||--E7--| 1110011111100111 E--| |--FC--| We now return you to the actual code... ~ Chapter 3 In Which We Are Encrypted ; now start looking for nibbles that ; don't really exist (except they do, ; because we're out of sync and reading ; timing bits as data) BB5E- BD 8C C0 LDA $C08C,X BB61- 10 FB BPL $BB5E BB63- 88 DEY BB64- F0 26 BEQ $BB8C BB66- C9 EE CMP #$EE BB68- D0 F4 BNE $BB5E BB6A- EA NOP BB6B- EA NOP ; now take the next (desynced) nibbles ; and store them in zp$F0..$F7 BB6C- A0 07 LDY #$07 BB6E- BD 8C C0 LDA $C08C,X BB71- 10 FB BPL $BB6E BB73- 99 F0 00 STA $00F0,Y BB76- EA NOP BB77- 88 DEY BB78- 10 F4 BPL $BB6E BB7A- A0 00 LDY #$00 ; take a desynchronized nibble (stored ; earlier in zero page) BB7C- A5 F4 LDA $F4 ; use it as a decryption key for the ; next stage of the bootloader BB7E- 59 00 B7 EOR $B700,Y BB81- 99 00 B7 STA $B700,Y BB84- 88 DEY BB85- D0 F5 BNE $BB7C ; continue to the (now decrypted) boot1 BB87- A6 2B LDX $2B BB89- 4C 00 B7 JMP $B700 ; The Badlands -- decrement the Death ; Counter and eventually give up and ; reboot BB8C- C6 FC DEC $FC BB8E- D0 86 BNE $BB16 BB90- EE F4 03 INC $03F4 BB93- 6C FC FF JMP ($FFFC) Well, this is inconvenient. But not insurmountable. I can interrupt the boot after the nibble check has decrypted the rest of the bootloader. Next steps, revised: 1. Capture decrypted boot1 sector 2. Write decrypted sector to disk 3. Patch boot0 to jump directly to (now decrypted) boot1, bypassing nibble check altogether ~ Chapter 4 In Which We Are Finished *9600