----------------Platoon---------------- A 4am crack 2016-09-25 --------------------------------------- Name: Platoon Genre: arcade Year: 1988 Credits: programming by L. Feddersen artwork by W. Holland, S. Chastain, A. Caberto, N. Nakamoto Publisher: Data East Platform: Apple //e or later (128K) Media: double-sided 5.25-inch floppy OS: Quick-DOS Previous cracks: one uncredited crack ~ Chapter 0 In Which Various Automated Tools Fail In Interesting Ways COPYA no errors, but copy reboots endlessly Locksmith Fast Disk Backup ditto EDD 4 bit copy (no sync, no count) ditto Copy ][+ nibble editor nothing suspicious nothing on track $23 hi-res disk scan shows no usage of half-tracks or quarter-tracks Disk Fixer custom bootloader on track $00 no sign of DOS, ProDOS, or Pascal operating system or disk catalog T00,S04 has "Quick-DOS(TM) Apple Program Loader" message, which is also displayed during boot Why didn't any of my copies work? Probably a runtime protection check in early boot. Disks do not simply reboot unless someone tells them to. Next steps: 1. Search for the runtime protection check (the "easy" way) 2. If that fails, trace the boot until I find it (the "hard" way) 3. If that fails, burn everything ~ Chapter 1 In Which We Get Lucky One thing that all protection checks have in common is they need to access the disk drive. Since most protection checks exploit edge cases of how bits are stored on disk, they need to use the lowest level access methods to manipulate those bits manually. The lowest level way to "read" a disk is the data latch softswitch address in the $C0xx range. For slot 6, it's $C0EC, but to allow disks to boot from any slot, developers usually use code like this: LDX LDA $C08C,X There's nothing that says you have to use the X-register as the index or the accumulator as the load register. But most disks do, out of convention I suppose (or 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 on disk, in memory, or both. 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 8C C0", which is the opcode sequence for "LDA $C08C,X". [Disk Fixer] ["F"ind] ["H"ex] ["BD 8C C0"] --v-- ------------- DISK SEARCH ------------- $00/$01-$1B $00/$02-$D2 $00/$02-$DB $00/$02-$E5 $00/$03-$03 $00/$07-$27 $00/$07-$37 $00/$07-$3C $00/$0E-$3D PRESS [RETURN] --^-- The match on sector $01 is part of a series of softswitches that initialize the drive; there is no other disk- related code nearby. The matches on sectors $02, $03, and $07 are part of the legitimate RWTS -- tight loops that wait for full nibbles coming out of the data latch, then matching sequences of "D5 AA 96" and "D5 AA AD" for the address and data prologues. But the match on sector $0E looks... suspicious. ----------- DISASSEMBLY MODE ---------- 0000:EA NOP 0001:EA NOP ; hard-code slot 6 -- very common for ; protection schemes (even if the real ; RWTS on the disk supports booting ; from any slot) 0002:A2 60 LDX #$60 ; some sort of Death Counter 0004:A9 56 LDA #$56 0006:85 FD STA $FD 0008:A9 08 LDA #$08 000A:C6 FC DEC $FC 000C:D0 04 BNE $0012 000E:C6 FD DEC $FD ; If the Death Counter hits zero, that ; would be bad. Which part of "Death ; Counter" sounded good to you, anyway? 0010:F0 38 BEQ $004A ; look for an #$FB nibble 0012:BC 8C C0 LDY $C08C,X 0015:10 FB BPL $0012 0017:C0 FB CPY #$FB 0019:D0 ED BNE $0008 ; 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) 001B:F0 00 BEQ $001D 001D:EA NOP 001E:EA NOP ; read data latch (note: no BPL loop ; here, we're just reading it once) 001F: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) 0022:C0 08 CPY #$08 ; rotate the carry into the low bit of ; the accumulator 0024:2A ROL ; if we just rolled a "1" bit out of ; the high bit of the accumulator, take ; this branch 0025:B0 0B BCS $0032 ; next nibble needs to be #$FF 0027:BC 8C C0 LDY $C08C,X 002A:10 FB BPL $0027 002C:C0 FF CPY #$FF ; ...otherwise we start over 002E:D0 D8 BNE $0008 ; loop back to get next nibble (this is ; an unconditional branch) 0030:F0 EB BEQ $001D ; execution continues here (from the ; "BCS" instruction 6 lines up) -- ; get another nibble 0032:BC 8C C0 LDY $C08C,X 0035:10 FB BPL $0032 ; stash it in zero page 0037:84 FC STY $FC ; if the accumulator is anything but ; the bit pattern %00001010, start over 0039:C9 0A CMP #$0A 003B:D0 CB BNE $0008 ; get one more nibble 003D:BD 8C C0 LDA $C08C,X 0040:10 FB BPL $003D ; more bit twiddling (apparently this ; and the previous nibble combine to ; form a single value, 4-4-encoded) 0042:38 SEC 0043:2A ROL 0044:25 FC AND $FC ; that 4-4-encoded value should be #$FF 0046:49 FF EOR #$FF 0048:F0 03 BEQ $004D ; otherwise we reboot slot 6 004A:4C 00 C6 JMP $C600 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. ~ Chapter 2 In Which We Get Visual 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: 00 START: 1BD4 LENGTH: 1814 1D28: DE BD 96 96 96 96 96 96 VIEW 1D30: 96 96 96 96 96 96 96 96 1D38: 96 96 96 96 96 96 96 EA 1D40: B6 FF DE AA EB FB BF FD 1D48: BB FB+FF FF+FF FF+FF+FF+ 1D50: FF+D5 AA 96 FF FE AA AA 1D58: AA AB FF FF DE AA EB FF+ 1D60: FF+FF+FF+FF+FF+D5 AA AD 1D68: A7 E9 DD 96 B6 F4 DC 9D --------------------------------------- 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 $1D49, which is between the end of one sector and the beginning of the next. (The data epilogue is at $1D42; the next address prologue is at $1D51.) 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 $1D49: - #$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: 00 START: 1BD4 LENGTH: 1814 1D28: DE BD 96 96 96 96 96 96 VIEW 1D30: 96 96 96 96 96 96 96 96 1D38: 96 96 96 96 96 96 96 EA 1D40: B6 FF DE AA EB FB BF FD 1D48: BB FB+FF FF FF+FF+FF+FF+ 1D50: FF+D5 AA 96 FF FE AA AA 1D58: AA AB FF FF DE AA EB FF+ 1D60: FF+FF+FF+FF+FF+D5 AA AD 1D68: A7 E9 DD 96 B6 F4 DC 9D --------------------------------------- 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 $1D49 now looks like this: - #$FB + timing bit - #$FF - #$FF - #$FF + timing bit - #$FF + timing bit This code is looking for #$FF nibbles with an alternating pattern of timing bit, no timing bit, timing bit, no timing bit. It doesn't find that on the bit copy, so it knows it's not running on an original disk. Returning to the code listing, we will branch to offset $004D if the check succeeds... ; set up zero page 004D:A9 60 LDA #$60 004F:85 2B STA $2B 0051:A9 09 LDA #$09 0053:85 27 STA $27 ; restore code from T00,S00 that called ; this routine 0055:A9 01 LDA #$01 0057:8D 00 08 STA $0800 005A:A9 A5 LDA #$A5 005C:8D 01 08 STA $0801 005F:A9 27 LDA #$27 0061:8D 02 08 STA $0802 0064:A9 C9 LDA #$C9 0066:8D 03 08 STA $0803 0069:A9 09 LDA #$09 006B:8D 04 08 STA $0804 ; and continue the boot as if nothing ; ever happened 006E:4C 01 08 JMP $0801 Ah! Looking at the boot sector, I see that it is designed to read 3 sectors instead of the usual 1 -- into $0800, $0900, and $0A00. So this protection check in T00,S0E will be loaded into $0A00, which is exactly where the boot sector jumps to: --v-- T00,S00 ----------- DISASSEMBLY MODE ---------- 0000:03 0001:4C 00 0A JMP $0A00 --^-- So this entire check is designed to be transparent and (almost) bootloader- independent. It has no side effects; the bootloader doesn't care that the protection check succeeded. In fact, it doesn't even realize it ran! ~ Chapter 3 In Which We Think About Automation Since the release of Passport https://archive.org/details/Passport4am I've changed the way I think about runtime protection checks like this. I used to think "what's the easiest way to defeat the code on this disk?" Now I think "what does the code on this disk have in common with code I've seen on other disks, and could I detect and defeat it automatically?" In this case, the easiest way to defeat the code on this disk is to bypass the check altogether and jump directly to the success path at $0A4D. However, I've seen this protection check on many other disks. What do they have in common? According to my records, the last two disks I cracked with this same check were #843 Zork Quest #684 Math Shop v1986-10-27 Some notable differences: - Bootloader/OS. Math Shop was ProDOS- based. Zork Quest was custom. This is Quick-DOS (although really it's more like a patch on top of Quick-DOS, not integrated into it). - When the protection check executes. This disk does it before even loading the RWTS from track 0. Math Shop did it as part of a .SYSTEM file (after all of ProDOS was loaded). Zork Quest loaded several tracks before calling. - Where the code starts within the sector. This code starts at offset $02. But on Math Shop, the code started at offset $9A. On Zork Quest, it was at offset $00. Note there are no absolute jumps within the code; it's all relocatable. That makes it slightly more difficult to find, but it's not insurmountable. - How the code is called. Sometimes the check is a self-contained subroutine where I could just put an "RTS" at the very beginning of it to bypass the check. But on this disk, there is more code that needs to execute after the check succeeds -- otherwise the disk will not boot! Similarities: - The actual magic bit sequence was on track 0 on all three disks. But that is not guaranteed; the caller could put the drive head on any track before calling the protection check. - Much of the code is similar, but it's also maddeningly different. To wit: ; hard-coded slot (also used by Zork ; Quest, but Math Shop used ProDOS ; globals to determine the boot slot) 0002:A2 60 LDX #$60 ; varies (other disks use other zero ; page addresses) 0004:A9 56 LDA #$56 0006:85 FD STA $FD 0008:A9 08 LDA #$08 000A:C6 FC DEC $FC 000C:D0 04 BNE $0012 000E:C6 FD DEC $FD ; varies (Math Shop / Zork Quest both ; use a branch length of #$3C because ; they have more code before the ; failure path) 0010:F0 38 BEQ $004A ; identical (including branch lengths) 0012:BC 8C C0 LDY $C08C,X 0015:10 FB BPL $0012 0017:C0 FB CPY #$FB 0019:D0 ED BNE $0008 001B:F0 00 BEQ $001D 001D:EA NOP 001E:EA NOP 001F:BC 8C C0 LDY $C08C,X 0022:C0 08 CPY #$08 0024:2A ROL 0025:B0 0B BCS $0032 0027:BC 8C C0 LDY $C08C,X 002A:10 FB BPL $0027 002C:C0 FF CPY #$FF 002E:D0 D8 BNE $0008 0030:F0 EB BEQ $001D 0032:BC 8C C0 LDY $C08C,X 0035:10 FB BPL $0032 ; varies (different zero page address) 0037:84 FC STY $FC ; identical (including branch lengths) 0039:C9 0A CMP #$0A 003B:D0 CB BNE $0008 003D:BD 8C C0 LDA $C08C,X 0040:10 FB BPL $003D 0042:38 SEC 0043:2A ROL ; varies (differnet zero page address) 0044:25 FC AND $FC ; identical 0046:49 FF EOR #$FF ; varies maddeningly (Zork Quest has a ; "BNE" branch to the failure path, but ; Math Shop has a "BEQ" branch to the ; failure path -- the exact opposite ; logic! They expect different 4-4- ; encoded values after the magic bit ; sequence!) 0048:F0 03 BEQ $004D ; This is obviously a failure path, but ; other disks have different failure ; behavior. 004A:4C 00 C6 JMP $C600 I'm still not sure I could automate the detection and proper patching of this protection check, in the general case. I can compensate for the different zero page addresses, and even the different branch lengths. But this disk demands a 4-4-encoded value of #$FF immediately after the magic bit sequence, and Math Shop demands any value except #$FF -- the exact opposite condition! To decide what the "correct" 4-4- encoded value is, I would need to examine a *lot* more samples. On this disk, the combination of "BEQ +3 / JMP $C600" makes it pretty clear that the correct value is #$FF. But Zork Quest didn't have the "JMP $C600", and Math Shop had a "BEQ" to the failure path! In the meantime, I have a working patch for this disk: jump to the success path instead of the start of the protection check. T00,S00,$02: 00 -> 4D ]PR#6 ...works... To be continued... Quod erat liberandum. --------------------------------------- A 4am crack No. 844 ------------------EOF------------------