---------------MicroChess-------------- A 4am crack 2019-12-08 --------------------------------------- Name: MicroChess Version: 2.0 Genre: board Year: 1978 Credits: Peter Jennings Publisher: Personal Software Platform: Apple ][ (24K *) Media: 5.25-inch disk Sides: 1 OS: custom (*) not a typo ~ Chapter 0 In Which Various Automated Tools Fail In Interesting Ways COPYA immediate disk read error Locksmith Fast Disk Backup unable to read any track EDD 4 bit copy (no sync, no count) works Copy ][+ nibble editor pure 13-sector disk ($D5 $AA $B5 / $D5 $AA $AD prologues) Disk Fixer can't read 13-sector disks Copy ][+ sector editor DOS 3.2 mode can't read any track, but DOS 3.2 PATCHED mode can read every track. The boot sectors on track 0 don't appear to be 6502 code. Most sectors on other tracks are the same data over and over. Why didn't COPYA work? 13-sector disk Why didn't Locksmith FDB work? ditto The game is a single loader. Once it's in memory, it never touches the disk again. Next steps: 1. Trace the boot 2. Capture the game code in memory 3. Write it out to a standard disk 4. Declare victory (*) (*) go to the gym ~ Chapter 1 The Googles, They Do Nothing This game was released just a few months after the original 13-sector Disk ][ drives were invented. Needless to say, it is a 13-sector disk without any fancy "hybrid" bootloader that would allow it to boot on 16-sector drives. Furthermore, neither Apple's BOOT13 utility nor the DOS 3.3 Basics disk -- whose entire purpose is to boot older 13-sector disks -- can boot this disk. Which is unusual and, if I may say so, wildly unhelpful. You had one job. Thus, I shall proceed with an original 13-sector drive and its original 13- sector drive controller card (under emulation, naturally, because nothing is real). This drive controller, like the 16- sector drive controller, can run from any address with a "6" in the right place. Thus: *9600 *301L ; standard 13-sector boot code creating ; a data table at $0800 0301- B9 00 08 LDA $0800,Y 0304- 0A ASL 0305- 0A ASL 0306- 0A ASL 0307- 99 00 08 STA $0800,Y 030A- C8 INY 030B- D0 F4 BNE $0301 030D- A6 2B LDX $2B 030F- A9 09 LDA #$09 0311- 85 27 STA $27 ; this is not normal 0313- 20 C1 03 JSR $03C1 *3C1L ; store a magic value in the I/O vector ; in zero page -- suspicious, because ; the I/O vector is 2 bytes, but we're ; only setting 1 of them, so I suspect ; this has nothing to do with the I/O ; vector at all 03C1- A9 11 LDA #$11 03C3- 85 38 STA $38 ; immediately wipe the value we just ; stored in zero page (from the above ; code, not from zero page) 03C5- A9 00 LDA #$00 03C7- 8D C2 03 STA $03C2 ; unconditional jump 03CA- F0 01 BEQ $03CD 03CC- 34 ??? ; this is the code that would normally ; appear at $0313, which called this ; routine 03CD- AD CC 03 LDA $03CC 03D0- 60 RTS OK, so we've set a magic value in zero page, in a location (I/O vector) that would get overwritten if we hit Reset, then self-modified to cover our tracks. Lovely. Continuing from $0316... ; standard 13-sector boot code -- ; ($40) points to the address where ; we're going to load the next few ; sectors ; ($3E) points to an entry point in ; the drive controller firmware to read ; those sectors 0316- 85 41 STA $41 0318- 84 40 STY $40 031A- 8A TXA 031B- 4A LSR 031C- 4A LSR 031D- 4A LSR 031E- 4A LSR 031F- 09 C0 ORA #$C0 0321- 85 3F STA $3F 0323- A9 5D LDA #$5D 0325- 85 3E STA $3E ; this will read a sector 0327- 20 43 03 JSR $0343 ; this will denibble-ize it 032A- 20 46 03 JSR $0346 ; this will let us know when we're done 032D- A5 3D LDA $3D 032F- 4D FF 03 EOR $03FF 0332- F0 06 BEQ $033A ; if we're not done, increment the ; target memory address (high byte) ; and the target sector ID and loop 0334- E6 41 INC $41 0336- E6 3D INC $3D 0338- D0 ED BNE $0327 *3FF 03FF- 0B *3CC 03CC- 34 This loop will read sectors into $3500- $3FFF. ; execution continues here (from $0332) ; once all additional sectors have been ; loaded from track 0 033A- 85 3E STA $3E 033C- AD CC 03 LDA $03CC 033F- EA NOP ; this is not normal (we would normally ; perturb the ($3E) vector and jump to ; it in order to jump to the start of ; the code we just read) 0340- 4C A5 03 JMP $03A5 *3A5L ; decrypt everything we just read into ; $3500-$3FFF 03A5- A9 35 LDA #$35 03A7- 85 49 STA $49 03A9- A0 00 LDY #$00 03AB- 84 48 STY $48 03AD- B1 48 LDA ($48),Y 03AF- 49 A5 EOR #$A5 03B1- 91 48 STA ($48),Y 03B3- C8 INY 03B4- D0 F7 BNE $03AD 03B6- E6 49 INC $49 03B8- A5 49 LDA $49 03BA- C9 40 CMP #$40 03BC- D0 EF BNE $03AD ; now jump to it 03BE- 4C 00 35 JMP $3500 And that, once again, is where I get to interrupt the boot. ~ Chapter 2 How Much Sum Could A Checksum Check If A Checksum Could Check Some Instead of simply breaking to the monitor after reading the boot sector, I want to allow it to execute -- read all the sectors it wants to read, plus decrypt them in memory -- then interrupt it at $03BE instead of jumping to $3500. *96F9:4C 00 97 9700- A9 59 LDA #$59 9702- 8D BF 03 STA $03BF 9705- A9 FF LDA #$FF 9707- 8D C0 03 STA $03C0 970A- 4C 01 03 JMP $0301 *9600G ...reboots slot 6... ...read read read... Now we should have the decrypted code at $3500-$3FFF. Let's take a look. *3500L ; at this point, X is the boot slot x16 3500- 8E CE 35 STX $35CE 3503- 8E C0 35 STX $35C0 3506- 20 76 35 JSR $3576 *3576L ; clear and display text screen 3576- D8 CLD 3577- 20 84 FE JSR $FE84 357A- 20 2F FB JSR $FB2F 357D- 20 93 FE JSR $FE93 3580- 20 58 FC JSR $FC58 ; print the title page (stored at $3600 ; as a string, cleverly using embedded ; lo-bit characters as HTAB commands) 3583- A0 00 LDY #$00 3585- B9 00 36 LDA $3600,Y 3588- 30 04 BMI $358E 358A- 85 24 STA $24 358C- 10 07 BPL $3595 358E- 84 F4 STY $F4 3590- 20 F0 FD JSR $FDF0 3593- A4 F4 LDY $F4 3595- C8 INY 3596- D0 ED BNE $3585 3598- A0 00 LDY #$00 359A- 60 RTS Continuing from $3509... ; set up normal DOS RWTS tables in the ; text page screen holes (these keep ; track of which disk track was last ; read) 3509- AD C0 35 LDA $35C0 350C- 4A LSR 350D- 4A LSR 350E- 4A LSR 350F- 4A LSR 3510- AA TAX 3511- A9 00 LDA #$00 3513- 8D 78 04 STA $0478 3516- 9D 78 04 STA $0478,X 3519- 9D F8 04 STA $04F8,X ; $3700 is an array of tracks/sectors ; to read 351C- B9 00 37 LDA $3700,Y *3700. 3700- 01 00 01 08 01 01 01 09 3708- 01 06 04 03 04 0B 04 04 3710- 04 0C 04 05 08 02 08 0A 3718- 08 07 08 00 08 08 0F 01 3720- 0F 09 0F 06 0F 03 0F 0B 3728- 12 04 12 0C 12 05 12 02 3730- 12 0A 12 07 16 00 16 08 3738- 16 01 16 09 00 00 00 00 It skips around the disk, reading a few sectors on track 1, 4, 5, 8, $0F, $12, then $16. The game itself doesn't read from disk once it's loaded, so I don't care about the specifics, but it's interesting to peer into the minds of developers making a copy protection for a device (the Disk ][ floppy drive) that had only existed for a few months. There were no bit copiers yet, so this disk was a complete black hole to all available third-party tools. Continuing from $351F... ; store the track in the RWTS parameter ; table 351F- 8D C3 35 STA $35C3 3522- C8 INY ; if both track and sector are 0, we're ; done 3523- 19 00 37 ORA $3700,Y 3526- F0 21 BEQ $3549 ; otherwise store the sector in the ; RWTS parameter table 3528- B9 00 37 LDA $3700,Y 352B- 8D C4 35 STA $35C4 352E- C8 INY 352F- 84 F4 STY $F4 ; this is extremely interesting -- ; we're taking the track number and ; XOR'ing it with the magic value in ; zero page that the boot sector put ; there earlier, then storing it... ; somewhere? 3531- AD C3 35 LDA $35C3 3534- 45 38 EOR $38 3536- 8D 8F 39 STA $398F To understand what just happened, we'll go look at the code around $398F. *398EL 398E- A9 00 LDA #$00 <-- 3990- 85 27 STA $27 3992- BD 8C C0 LDA $C08C,X 3995- 10 FB BPL $3992 3997- 2A ROL 3998- 85 26 STA $26 399A- BD 8C C0 LDA $C08C,X 399D- 10 FB BPL $399A 399F- 25 26 AND $26 39A1- 99 2C 00 STA $002C,Y 39A4- 45 27 EOR $27 39A6- 88 DEY 39A7- 10 E7 BPL $3990 39A9- A8 TAY 39AA- D0 B7 BNE $3963 <-- This is the address field parser. Every sector on a 13-sector disk (and, later, 16-sector disks) has an address field containing disk volume, track number, sector number, and a checksum. XOR'ing all of those values must yield 0, or else the sector is considered corrupt and the read operation fails. That "0" is what we just changed. It's initialized at $398E and checked at $39AA. Instead of 0, this disk requires a different non-zero value on each track -- and the exact value is calculated from the magic number that was stored in the I/O vector during early boot. Also, this is why BOOT13 can't boot the original disk. Unlike the drive controller firmware, BOOT13 insists on verifying the address field checksum for track 0, sector 0. Since that checksum is intentionally corrupted, it never finds a valid boot sector. That's great. Copy protection is great. ~ Chapter 3 A Checksum Would Check As Much As It Could Check If A Checksum Could Check Some Continuing from $3539... ; read the sector 3539- A9 35 LDA #$35 353B- A0 BF LDY #$BF 353D- 20 00 3D JSR $3D00 3540- B0 59 BCS $359B 3542- A4 F4 LDY $F4 ; increment the target memory address ; (high byte) 3544- EE C8 35 INC $35C8 ; this acts as an unconditional branch, ; since we'll never get as far as ; reading into $FF00 (if we even got as ; far as $3500, we would overwrite this ; code) 3547- D0 D3 BNE $351C ; execution continues here (from $3526) ; once the entire game is read into ; memory -- ; show hi-res page 1 3549- AD 57 C0 LDA $C057 354C- AD 50 C0 LDA $C050 354F- AD 52 C0 LDA $C052 ; decrypt and move the entire game code ; to slightly lower memory (from $0800+ ; to $0200+) 3552- A9 02 LDA #$02 3554- 85 F1 STA $F1 3556- A9 08 LDA #$08 3558- 85 F3 STA $F3 355A- A0 00 LDY #$00 355C- 84 F0 STY $F0 355E- 84 F2 STY $F2 3560- B1 F2 LDA ($F2),Y 3562- 49 F0 EOR #$F0 3564- 91 F0 STA ($F0),Y 3566- C8 INY 3567- D0 F7 BNE $3560 3569- E6 F1 INC $F1 356B- E6 F3 INC $F3 356D- A5 F1 LDA $F1 356F- C9 20 CMP #$20 3571- D0 ED BNE $3560 ; start the game 3573- 4C 00 06 JMP $0600 One final boot trace should be able to capture the entire game in memory. ; set up callback 9700- A9 0D LDA #$0D 9702- 8D BF 03 STA $03BF 9705- A9 97 LDA #$97 9707- 8D C0 03 STA $03C0 ; start the boot 970A- 4C 01 03 JMP $0301 ; callback is here -- ; change the copy/decrypt loop so it ; decrypts the game into $4200 instead ; of $0200 970D- A9 42 LDA #$42 970F- 8D 53 35 STA $3553 ; stop at $6000 instead of $2000 9712- A9 60 LDA #$60 9714- 8D 70 35 STA $3570 ; break to monitor instead of jumping ; to game code 9717- A9 59 LDA #$59 9719- 8D 74 35 STA $3574 971C- A9 FF LDA #$FF 971E- 8D 75 35 STA $3575 ; continue the boot 9721- 4C 00 35 JMP $3500 *9600G ...boots slot 6... ...read read read... Now we have the entire (decrypted!) game code at $4200-$5FFF. Rebooting to my work disk in slot 5 (which is really just a .dsk file mounted in an emulator, because -- and I can not stress this enough -- nothing is real), I can save all this work. To disk. As one does. [S5,D1=my work disk] *C500G ...boots slot 5... ]BSAVE OBJ.0200-1FFF,A$4200,L$1E00 ]BSAVE OBJ.3500-3FFF,A$3500,L$B00 Now, a simple write routine to write the game to a blank disk. The entire game fits on tracks 1 and 2, with room to spare. ; straightforward multi-sector write ; loop, via the RWTS vector at $03D9 08C0- A9 08 LDA #$08 08C2- A0 E8 LDY #$E8 08C4- 20 D9 03 JSR $03D9 08C7- AC ED 08 LDY $08ED 08CA- 88 DEY 08CB- 10 05 BPL $08D2 08CD- A0 0F LDY #$0F 08CF- CE EC 08 DEC $08EC 08D2- 8C ED 08 STY $08ED 08D5- CE F1 08 DEC $08F1 08D8- CE E1 08 DEC $08E1 08DB- D0 E3 BNE $08C0 08DD- 60 RTS +-- sector count v 08E0- 17 1D 0A 1B E8 B7 00 B4 08E8- 01 60 01 00 02 0D FB 08 ^ ^ track --+ +-- sector 08F0- 00 5F 00 00 02 00 FE 60 ^ +-- starting address 08F8- 01 00 00 00 01 EF D8 00 *BSAVE WRITER,A$8C0,L$40 [S6,D1=blank disk] *8C0G ...write write write... ~ Chapter 4 Finishing Touches Of course this disk can't boot yet, because it has no bootloader. We'll borrow the boot sector and RWTS from a DOS 3.3 master disk. This has the added benefit of loading at $3800, in the same place the original disk loaded its RWTS, so the cracked version will still run on a 24K Apple ][. [S6,D2=DOS 3.3 master] [Disk Fixer] [Copy sectors 0-9 from drive 2 to 1] I also want to reproduce the title screen with credits during loading. There's a free sector on T00,S05, which is loaded at $3B00. (It's used by the RWTS as scratch space while reading sectors, but you can put whatever you like there as long as you use it before reading any sectors.) This is the perfect place to store the title text. There's plenty of room in a DOS-3.3- bootloader-that-doesn't-load-DOS for the print routine. I put it at $085D and called it from $0845, after the RWTS is loaded from track 0. It's functionally identical to the routine at $3576 on the original disk. --v-- T00,S00 ----------- DISASSEMBLY MODE ---------- 0039:EE FE 08 INC $08FE 003C:EE FE 08 INC $08FE 003F:20 89 FE JSR $FE89 0042:20 93 FE JSR $FE93 0045:20 5D 08 JSR $085D ----+ 0048:A6 2B LDX $2B | 004A:6C FD 08 JMP ($08FD) | ... | 005D:20 2F FB JSR $FB2F <---+ 0060:20 58 FC JSR $FC58 0063:A0 00 LDY #$00 0065:B9 00 3B LDA $3B00,Y 0068:30 04 BMI $006E 006A:85 24 STA $24 006C:10 07 BPL $0075 006E:84 F4 STY $F4 0070:20 F0 FD JSR $FDF0 0073:A4 F4 LDY $F4 0075:C8 INY 0076:D0 ED BNE $0065 0078:60 RTS --^-- T00,S01 has some minor modifications to load the game from tracks 1 and 2 into $0800-$25FF, like the original disk. After that, we can use the original game code (minus the decryption) to move it down to lower memory. --v-- T00,S01 ----------- DISASSEMBLY MODE ---------- ; read game 0038:20 93 37 JSR $3793 ; reset stack 003B:A2 FF LDX #$FF 003D:9A TXS ; original game code (minus decryption) 003E:AD 57 C0 LDA $C057 0041:AD 50 C0 LDA $C050 0044:AD 52 C0 LDA $C052 0047:A9 02 LDA #$02 0049:85 F1 STA $F1 004B:A9 08 LDA #$08 004D:85 F3 STA $F3 004F:A0 00 LDY #$00 0051:84 F0 STY $F0 0053:84 F2 STY $F2 0055:B1 F2 LDA ($F2),Y 0057:91 F0 STA ($F0),Y 0059:C8 INY 005A:D0 F9 BNE $0055 005C:E6 F1 INC $F1 005E:E6 F3 INC $F3 0060:A5 F1 LDA $F1 0062:C9 20 CMP #$20 0064:D0 EF BNE $0055 ; start game 0066:4C 00 06 JMP $0600 --^-- Plenty of room left on the disk to make an animated crack screen with sound effects and shout outs! (I refrained.) Quod erat liberandum. --------------------------------------- A 4am crack No. 2129 ------------------EOF------------------