---------------Simon Says-------------- A 4am crack 2015-05-14 -------------------. updated 2015-09-04 |___________________ Name: Simon Says... Genre: educational Year: 1989 Authors: Donna Stanger (design); Jim McDonagh (programming) Publisher: Sunburst Communications Media: single-sided 5.25-inch floppy OS: ProDOS 1.7 Other versions: Asimov has the 3.5-inch version, but the 5.25-inch version is preserved here for the first time ~ Chapter 0 In Which Various Automated Tools Fail In Interesting Ways COPYA no errors, but copy loads ProDOS and reboots Locksmith Fast Disk Backup ditto EDD 4 bit copy (no sync, no count) ditto Why didn't any of my copies work? probably a nibble check in the first .SYSTEM file Next steps: 1. BLOAD first .SYSTEM file 2. Find nibble check and disable it 3. There is no step 3 (I hope) ~ Chapter 1 In Which We Hit A Small Bump [S6,D1=original disk] [S7,D1=my ProDOS hard drive] ]PR#7 ... ]CAT,S6,D1 /SIMON.SAYS NAME TYPE BLOCKS MODIFIED PRODOS SYS 32 4-APR-89 LOGO BIN 17 3-FEB-89 SIMON BIN 13 27-JUN-89 GETOP BIN 5 28-JUN-89 LOADER.SYSTEM SYS 4 20-JUN-89 *KEYPAK BIN 8 8-JUN-89 PUTLET BIN 4 8-JUN-89 *BIGLETS BIN 11 8-JUN-89 OPTIONS BIN 1 28-JUN-89 CGWL BIN 12 8-JUN-89 HANDYS BIN 3 23-JUN-89 CALIB BIN 8 27-JUN-89 INSTRU BIN 4 20-JUN-89 *HEADER BIN 3 8-JUN-89 BLOCKS FREE: 148 BLOCKS USED: 132 ]PREFIX /SIMON.SAYS ]BLOAD LOADER.SYSTEM,A$2000,TSYS ]CALL -151 *2000L 2000- A0 00 LDY #$00 2002- B9 00 21 LDA $2100,Y 2005- 99 00 B7 STA $B700,Y 2008- B9 00 22 LDA $2200,Y 200B- 99 00 B8 STA $B800,Y 200E- B9 00 23 LDA $2300,Y 2011- 99 00 B9 STA $B900,Y 2014- B9 00 24 LDA $2400,Y 2017- 99 00 BA STA $BA00,Y 201A- B9 00 25 LDA $2500,Y 201D- 99 00 BB STA $BB00,Y 2020- B9 00 26 LDA $2600,Y 2023- 99 00 BC STA $BC00,Y 2026- C8 INY 2027- D0 D9 BNE $2002 2029- 4C 00 B7 JMP $B700 *2029:60 *2000G ...hangs... Um, that shouldn't fail. It's the world's simplest memory copy loop. Oh, I see. This is copying into memory ranges that are used by BASIC.SYSTEM. The memory copy didn't fail; the RTS did, because it tried to return to a spot in BASIC.SYSTEM that we just overwrote. Grr. ]PR#7 ... ]PREFIX /SIMON.SAYS ]BLOAD LOADER.SYSTEM,A$2000,TSYS ]CALL -151 ; disconnect ProDOS and BASIC.SYSTEM *FE89G FE93G ; set an RTS after the copy loop *2029:60 ; do the memcpy *2000G Here we go. ~ Chapter 2 In Which We Set Off In Search Of Copy Protection, And We Find More Than We Bargained For *B700L ; standard ProDOS-y stuff B700- A0 17 LDY #$17 B702- A9 00 LDA #$00 B704- 99 58 BF STA $BF58,Y B707- 88 DEY B708- 10 FA BPL $B704 ; several JSRs and a branch -- always a ; good place to start investigating B70A- 20 B2 B8 JSR $B8B2 B70D- 20 9E B8 JSR $B89E B710- 20 F4 B8 JSR $B8F4 B713- B0 08 BCS $B71D B715- 20 00 BF JSR $BF00 B718- [C6 42 B9] B71B- 90 03 BCC $B720 ; I'm guessing this is The Badlands B71D- 4C 45 B9 JMP $B945 *B8B2L . . more ProDOS-y stuff (harmless) . *B89EL ; probably an address B89E- A9 00 LDA #$00 B8A0- 85 84 STA $84 B8A2- A9 1D LDA #$1D B8A4- 85 85 STA $85 ; get slot+drive B8A6- AD 30 BF LDA $BF30 ; another JSR + branch -- mildly ; suspicious B8A9- 20 74 B9 JSR $B974 B8AC- 90 03 BCC $B8B1 ; and if it fails, jump to The Badlands ; (definitely suspicious) B8AE- 4C 45 B9 JMP $B945 B8B1- 60 RTS *B974L ; save zero page $00 B974- A5 00 LDA $00 B976- 48 PHA ; get slot+drive B977- AD 30 BF LDA $BF30 ; just the slot number B97A- 29 70 AND #$70 B97C- 85 00 STA $00 ; JSR + branch again B97E- 20 98 B9 JSR $B998 B981- B0 0F BCS $B992 This first JSR looks like some sort of compatibility check of the floppy drive controller ROM: *B998L ; munge slot number into a $C6 byte (if ; booting from slot 6) B998- 4A LSR B999- 4A LSR B99A- 4A LSR B99B- 4A LSR B99C- 09 C0 ORA #$C0 B99E- 8D BD B9 STA $B9BD ; check that $C601 is $20 B9A1- A0 01 LDY #$01 B9A3- A9 20 LDA #$20 B9A5- 20 BB B9 JSR $B9BB ; and $C603 is $00 B9A8- A9 00 LDA #$00 B9AA- 20 BB B9 JSR $B9BB ; and $C605 is $03 B9AD- A9 03 LDA #$03 B9AF- 20 BB B9 JSR $B9BB ; and $C6FF is $00 B9B2- A0 FF LDY #$FF B9B4- A9 00 LDA #$00 B9B6- 20 BB B9 JSR $B9BB B9B9- 18 CLC B9BA- 60 RTS B9BB- D9 00 C1 CMP $C100,Y B9BE- D0 03 BNE $B9C3 B9C0- C8 INY B9C1- C8 INY B9C2- 60 RTS B9C3- 68 PLA B9C4- 68 PLA B9C5- 38 SEC B9C6- 60 RTS I think this is a protection against users installing the program on a hard drive. I checked the ROM of a real Apple floppy drive and a CFFA 3000 virtual drive. They each pass this test, but my hard drive ROM fails. Popping and continuing from $B983... *B983L ; try something three times B983- A9 03 LDA #$03 B985- 8D 97 B9 STA $B997 B988- 20 C7 B9 JSR $B9C7 ; branch to exit routine if carry clear B98B- 90 06 BCC $B993 ; otherwise decrement counter and try ; again B98D- CE 97 B9 DEC $B997 B990- D0 F6 BNE $B988 ; failure path is here (from $B981, or ; from falling through after the $B997 ; counter hits 0) B992- 38 SEC ; restore zero page $00 and exit with ; carry cleared (success) or set (fail) B993- 68 PLA B994- 85 00 STA $00 B996- 60 RTS Down the rabbit hole we go... ~ Chapter 3 In Which We Go Down The Rabbit Hole And Find A Familiar-Looking Hare *B9C7L ; get slot+drive B9C7- AD 30 BF LDA $BF30 B9CA- 8D E6 B9 STA $B9E6 ; check online status (ProDOS MLI $C5) B9CD- 20 00 BF JSR $BF00 B9D0- [C5 E5 B9] B9D3- 08 PHP B9D4- 78 SEI ; try something B9D5- 20 FE B9 JSR $B9FE ; branch to exit routine if carry clear B9D8- 90 08 BCC $B9E2 ; try something else B9DA- 20 FA B9 JSR $B9FA ; branch to exit routine if carry clear B9DD- 90 03 BCC $B9E2 ; fail x2 -- set carry and exit B9DF- 28 PLP B9E0- 38 SEC B9E1- 60 RTS ; success -- clear carry and exit B9E2- 28 PLP B9E3- 18 CLC B9E4- 60 RTS *B9FEL ; this actually changes a branch ; instruction (see below) B9FE- A9 16 LDA #$16 BA00- 8D 63 BA STA $BA63 ; turn on drive motor manually (always ; suspicious) BA03- A6 00 LDX $00 BA05- BD 89 C0 LDA $C089,X BA08- BD 8E C0 LDA $C08E,X BA0B- 18 CLC ; set up direct data latch reads later BA0C- A5 00 LDA $00 BA0E- 69 8C ADC #$8C BA10- 8D 24 BA STA $BA24 BA13- 8D A2 BA STA $BAA2 ; initialize death counter BA16- A0 E0 LDY #$E0 BA18- 8C A7 BA STY $BAA7 BA1B- C8 INY BA1C- D0 05 BNE $BA23 ; if death counter hits 0, branch to ; failure path and exit BA1E- EE A7 BA INC $BAA7 BA21- F0 77 BEQ $BA9A ; look for address prologue BA23- AD EC C0 LDA $C0EC BA26- 10 FB BPL $BA23 BA28- C9 D5 CMP #$D5 BA2A- D0 EF BNE $BA1B BA2C- 20 A1 BA JSR $BAA1 BA2F- C9 AA CMP #$AA BA31- D0 F5 BNE $BA28 BA33- 20 A1 BA JSR $BAA1 BA36- C9 96 CMP #$96 BA38- D0 EE BNE $BA28 ; skip several nibbles (inside address ; field) BA3A- 20 A1 BA JSR $BAA1 BA3D- 20 A1 BA JSR $BAA1 ; look for "AA AA AA AA" inside address ; field (that's T00,S00, 4-4 encoded) BA40- A2 03 LDX #$03 BA42- 20 A1 BA JSR $BAA1 BA45- C9 AA CMP #$AA BA47- D0 D2 BNE $BA1B BA49- CA DEX BA4A- 10 F6 BPL $BA42 ; skip rest of address field BA4C- 20 A1 BA JSR $BAA1 BA4F- 20 A1 BA JSR $BAA1 ; match 2/3 of address epilogue (DE AA) BA52- 20 A1 BA JSR $BAA1 BA55- C9 DE CMP #$DE BA57- D0 C2 BNE $BA1B BA59- A6 00 LDX $00 BA5B- BD 8C C0 LDA $C08C,X BA5E- 10 FB BPL $BA5B BA60- C9 AA CMP #$AA ; this branch instruction was modified ; at $BA00 -- by the time execution ; reaches here, it will actually branch ; to $BA7A BA62- F0 02 BEQ $BA66 BA64- D0 B5 BNE $BA1B ;[skipped] ;BA66- A0 02 LDY #$02 ;BA68- BD 8D C0 LDA $C08D,X ;BA6B- BD 8C C0 LDA $C08C,X ;BA6E- 10 FB BPL $BA6B ;BA70- C9 BB CMP #$BB ;BA72- F0 1B BEQ $BA8F ;BA74- 88 DEY ;BA75- 10 F4 BPL $BA6B ;BA77- 4C 1E BA JMP $BA1E ; execution continues here (from $BA62) ; kill some time to get out of sync ; with the "proper" start of nibbles BA7A- A0 02 LDY #$02 BA7C- BD 8D C0 LDA $C08D,X BA7F- 48 PHA BA80- 68 PLA ; now look for nibbles that don't ; really exist (except they do, because ; we're out of sync and reading timing ; bits as data) BA81- BD 8C C0 LDA $C08C,X BA84- 10 FB BPL $BA81 BA86- C9 BB CMP #$BB BA88- F0 05 BEQ $BA8F BA8A- 88 DEY BA8B- 10 F4 BPL $BA81 BA8D- 30 E8 BMI $BA77 BA8F- BD 8C C0 LDA $C08C,X BA92- 10 FB BPL $BA8F BA94- C9 F9 CMP #$F9 BA96- D0 DF BNE $BA77 ; success falls through to here BA98- 18 CLC ; failure path is at $BA9A (hidden in ; this listing by a "BIT" instruction) ; but opcode $38 is "SEC", so it will ; set the carry BA99- 24 38 BIT $38 ; turn off drive motor and exit via ; RTS BA9B- A6 00 LDX $00 BA9D- BD 88 C0 LDA $C088,X BAA0- 60 RTS ; subroutine (called frequently) to ; get a nibble BAA1- AD EC C0 LDA $C0EC BAA4- 10 FB BPL $BAA1 BAA6- 60 RTS This nibble check clears the carry on success and sets the carry on failure, then it always returns to the caller. It has no other side effects. Looking at the call chain, the first subroutine that did nothing but copy protection was at $B974. (The caller before that was setting some zero page locations, which might be legitimate.) So, I can change the routine at $B974 to unconditionally clear the carry and return, and that result will filter back up the call chain and everyone will think the nibble check passed. T09,S06,$74 change "A5 00" to "18 60" ~ Postscript I've cracked over 300 disks to date, many of which use similar techniques to determine if a disk is original. But I've never TRULY understood how they do that -- at least, not well enough to explain it. So here goes nothing. The routine on this disk is looking at some nibbles at the end of the address field of T00,S00. Here's what that looks like in a nibble editor: --v-- COPY ][ PLUS BIT COPY PROGRAM 8.4 (C) 1982-9 CENTRAL POINT SOFTWARE, INC. --------------------------------------- TRACK: 00 START: 1800 LENGTH: 3DFF 41C8: FF FF FF FF FF FF FF FF VIEW 41D0: FF FF FF FF FF FF FF FF 41D8: FF FF FF FF FF FF FF FF 41E0: FF FF FF FF D5 AA 96 AA ^^^^^^^^ address prologue 41E8: AB AA AA AA AA AA AB DE <-41E9 41F0: AA EB 97 DF FF E7 F9 FE ^^ ^^ ^^ important 41F8: FF FF D5 AA AD AC B6 ED 4200: F2 FF 9E B7 FB D9 F9 FD FIND: 4208: F6 F9 DA EF EE 9D B9 ED AA AA AA --------------------------------------- A TO ANALYZE DATA ESC TO QUIT ? FOR HELP SCREEN / CHANGE PARMS Q FOR NEXT TRACK SPACE TO RE-READ --^-- On the original disk, the three nibbles I highlighted are displayed in inverse, meaning that each one is followed by one or two "timing bits" (extra 0 bits in the raw bitstream on disk). Since every nibble read from disk must have its high bit set (i.e. be greater than or equal to $80), a normal RWTS would simply ignore the extra "timing" bits. They would get swallowed by the standard "wait for data latch to be at least $80" loop, which I've seen many times: :1 LDA $C08C,X BPL :1 But let's break down those nibbles and take a closer look. Here they are, as they actually appear on disk (in a continuous bitstream): $EB $97 $DF $FF /------\ /------\ /------\ /------\ 111010110100101110110111110011111111 ^ ^ ^^ (these 4 zeroes are the timing bits) 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) $EB $97 $DF $FF /------\ /------\ /------\ /------\ 111010110100101110110111110011111111 XXX \------/\------/ \------/ $B4 $BB $F9 (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 "ignored" timing bits are no longer being ignored; now they're being interpreted as part of the nibbles that are being returned to the higher level code. Also, some "real" bits are being treated as timing bits, like the one before $B4, or the one after $BB. Now, let's pretend those 3 timing bits are absent. Then the raw bitstream would look like this: 11101011100101111101111111111111 Depending on when we start, this too can be interpreted in different ways: (normal start) $EB $97 $DF $FF /------\/------\/------\/------\ 11101011100101111101111111111111 XXX \------/ \------/\------/ $B9 $FB $FF (delayed start) If we start reading right away, the nibbles we get are... exactly the same: $EB $97 $DF $FF. But if we delay a few cycles and get out of sync with the "proper" start of nibbles, the results we get are completely different! $BB never appears in the delayed bitstream; neither does $F9. Generic bit copiers won't preserve these timing bits. Advanced bit copiers might preserve one timing bit after each nibble, but not two. For example, if an advanced bit copier preserved one timing bit after each nibble (but not both after $DF), the bitstream would look like this: (normal start) $EB $97 $DF $FF /------\ /------\ /------\ /------\ 11101011010010111011011111011111111 XXX \------/\------/ \------/ $B4 $BB $FB (delayed start) The desynchronized $BB matches, but the next desynchronized nibble after that, which "should" be $F9, is now $FB! By wasting a few CPU cycles at just the right time, then interpreting timing bits as data, developers could check if you were booting from an original disk. Quod erat liberandum. ~ Changelog 2015-09-04 - Fixed reverse-engineered bitstream and an unrelated typo in the explanation text. [h/t qkumba] 2015-05-14 - initial release --------------------------------------- A 4am crack No. 313 ------------------EOF------------------