----------------G.I. Joe--------------- A 4am crack 2015-08-24 --------------------------------------- Name: G.I. Joe Genre: arcade Year: 1985 Authors: Jeff Johannigman, Ray Carpenter, Michael Kosaka (graphics), K-Byte (Apple version) Publisher: Epyx Media: double-sided 5.25-inch floppy OS: custom with DOS 3.3 bootloader Previous cracks: Gadget Master / Five Star The Blade Side B just says "PLEASE BOOT OTHER SIDE" and hangs, so I should probably start with side A. ~ Chapter 0 In Which Various Automated Tools Fail In Interesting Ways COPYA immediate read error, but it gets a participation medal for showing up Locksmith Fast Disk Backup unable to read any track EDD 4 bit copy (no sync, no count) no read errors, but 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 Are Surprised [S6,D1=original disk] [S5,D1=my work disk] ]PR#5 CAPTURING BOOT0 ...reboots slot 6... ...reboots slot 5... SAVING BOOT0 ]BLOAD BOOT0,A$800 ]CALL -151 *801L 0801- A5 27 LDA $27 0803- C9 09 CMP #$09 0805- D0 18 BNE $081F 0807- 20 B3 08 JSR $08B3 <-- ! 080A- 4A LSR 080B- 4A LSR 080C- 4A LSR 080D- 09 C0 ORA #$C0 080F- 85 3F STA $3F 0811- A9 5C LDA #$5C 0813- 85 3E STA $3E This looks like a normal DOS 3.3 boot0, except for that suspicious call to $08B3 in the first-run part of the loop (at $0807). *8B3L ; read/write RAM bank 1 08B3- AD 8B C0 LDA $C08B 08B6- AD 8B C0 LDA $C08B ; and do the thing I was expecting to ; see at $807 (before we called this ; subroutine) 08B9- A5 2B LDA $2B 08BB- 4A LSR 08BC- 60 RTS Poking around a bit further, the light dawns: *8FE.8FF 08FE- D3 0D This disk is loading boot1 straight into the language card, starting at $D300. That's why it had to set the RAM bank for read/write access -- because it's going to read and write to it. (Such things are obvious in hindsight.) The rest of boot0 is unsurprising. It ends up here: 083F- 4C BD 08 JMP $08BD *8BDL ; dunno 08BD- A9 FF LDA #$FF 08BF- 8D F8 1F STA $1FF8 ; unfriendly reset vector 08C2- A9 00 LDA #$00 08C4- 8D F2 03 STA $03F2 08C7- 8D FC FF STA $FFFC 08CA- A9 C6 LDA #$C6 08CC- 8D F3 03 STA $03F3 08CF- 8D FD FF STA $FFFD 08D2- 49 A5 EOR #$A5 08D4- 8D F4 03 STA $03F4 ; read/write on the RAM bank again 08D7- AD 8B C0 LDA $C08B 08DA- AD 8B C0 LDA $C08B ; continue to boot1 08DD- A6 2B LDX $2B 08DF- 6C FD 08 JMP ($08FD) And that's where I need to interrupt the boot. *9600 - Advance to next parm - Exit edit mode R - Restore DOS 3.3 parameters O - Edit Original disk's parameters C - Edit Copy disk's parameters G - Begin demuffin process --^-- Pressing "G" switches to the Locksmith Fast Disk Copy UI. It assumes that both disks are in slot 6, and that drive 1 is the original and drive 2 is the copy. [S6,D1=original disk] [S6,D2=blank disk] --v-- LOCKSMITH 7.0 FAST DISK BACKUP R................................... W*********************************** HEX 00000000000000001111111111111111222 TRK 0123456789ABCDEF0123456789ABCDEF012 0................................... 1................................... 2................................... 3................................... 4................................... 5................................... 6................................... 7................................... 8................................... 9................................... A................................... B................................... C................................... D................................... 12 E................................... F................................... [ ] PRESS [RESET] TO EXIT --^-- Side B converts the same way. [S6,D1=demuffin'd copy (side A)] ]PR#6 ...reboots endlessly... Let's go find that nibble check. ~ Chapter 3 In Which We Are Desynchronized ]PR#5 ... ]BLOAD BOOT1 D300-DFFF,A$2300 ]CALL -151 ; copy ROM to RAM bank 1 *C08D C08D F800 $D49C, which ; would be later in this sector D40C- A9 9C LDA #$9C D40E- 85 F6 STA $F6 D410- A9 D4 LDA #$D4 D412- 85 F7 STA $F7 ; probably a Death Counter D414- A9 80 LDA #$80 D416- 85 F5 STA $F5 ; if Death Counter hits 0, jump to ; The Badlands D418- C6 F5 DEC $F5 D41A- F0 76 BEQ $D492 ; this gets the next available address ; field (like $B944 in DOS 3.3) D41C- 20 A4 D4 JSR $D4A4 ; if that failed, off to The Badlands D41F- B0 71 BCS $D492 ; zp$F1 holds the sector number D421- A5 F1 LDA $F1 ; was it the sector we wanted? D423- C9 0F CMP #$0F ; nope, loop back and try again D425- D0 F1 BNE $D418 ; look for $D5 nibble D427- A0 00 LDY #$00 D429- BD 8C C0 LDA $C08C,X D42C- 10 FB BPL $D429 D42E- 88 DEY D42F- F0 61 BEQ $D492 D431- C9 D5 CMP #$D5 D433- D0 F4 BNE $D429 ; look for $E7 nibble D435- A0 00 LDY #$00 D437- BD 8C C0 LDA $C08C,X D43A- 10 FB BPL $D437 D43C- 88 DEY ; fail if we don't find it in time D43D- F0 53 BEQ $D492 D43F- C9 E7 CMP #$E7 D441- D0 F4 BNE $D437 ; look for two more $E7 nibbles D443- BD 8C C0 LDA $C08C,X D446- 10 FB BPL $D443 D448- C9 E7 CMP #$E7 D44A- D0 46 BNE $D492 ; fail D44C- BD 8C C0 LDA $C08C,X D44F- 10 FB BPL $D44C D451- C9 E7 CMP #$E7 D453- D0 3D BNE $D492 ; fail ; kill some time to get out of sync ; with the "proper" start of nibbles ; (see below) D455- BD 8D C0 LDA $C08D,X D458- A0 10 LDY #$10 D45A- 24 06 BIT $06 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 4 In Which We Are Finished ; 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) D45C- BD 8C C0 LDA $C08C,X D45F- 10 FB BPL $D45C D461- 88 DEY D462- F0 2E BEQ $D492 D464- C9 EE CMP #$EE D466- D0 F4 BNE $D45C ; compare the next 8 nibbles to an ; array stored at ($F6) [= $D49C] ; in reverse order D468- A0 07 LDY #$07 D46A- BD 8C C0 LDA $C08C,X D46D- 10 FB BPL $D46A D46F- D1 F6 CMP ($F6),Y ; if any nibble doesn't match, off to ; The Badlands D471- D0 1F BNE $D492 D473- 88 DEY D474- 10 F4 BPL $D46A ; success falls through to here -- ; set up zero page to read the sector ; that was supposed to be at $D400 in ; the first place D476- A2 60 LDX #$60 D478- 8E 01 08 STX $0801 ; zp$2B = boot slot (x16) D47B- A6 2B LDX $2B ; zp$3D = physical sector number D47D- A9 02 LDA #$02 D47F- 85 3D STA $3D ; zp$27 = target page D481- A9 D4 LDA #$D4 D483- 85 27 STA $27 ; manually push $D3FF to the stack D485- A9 D3 LDA #$D3 D487- 48 PHA D488- A9 FF LDA #$FF D48A- 48 PHA ; manually push $Cx5B to the stack D48B- A5 3F LDA $3F D48D- 48 PHA D48E- A9 5B LDA #$5B D490- 48 PHA ; now exit via RTS, which will call ; the disk controller ROM routine at ; $Cx5C to read the "real" sector into ; $D400, then jump to $0801, which is ; now an "RTS" (set at $D478), which ; will pop the next address off the ; stack and "return" to $D400, which ; will continue the boot D491- 60 RTS ; The Badlands -- decrement Death ; Counter and eventually reboot D492- C6 F4 DEC $F4 D494- D0 03 BNE $D499 D496- 4C 00 C6 JMP $C600 D499- 4C 14 D4 JMP $D414 Finally, the array of desynchronized nibbles (the E7 bitstream, in reverse order): D49C- [FC EE EE FC E7 EE FC E7] There are two ways to go here. I could wipe out this entire sector and replace it with the code that's supposed to be loaded at $D400 in the first place. Or, I could do the Simplest Thing That Could Possibly Work by patching this sector to skip over the nibble check and jump directly to the success path. I'm going with option #2. T00,S01,$02 change "85 F4" to "D0 72" Quod erat liberandum. --------------------------------------- A 4am crack No. 421 ------------------EOF------------------