---------------MultiPloy--------------- A 4am crack 2017-05-06 --------------------------------------- Name: MultiPloy Genre: educational Year: 1982 Credits: Paul Coletta Publisher: Reston Publishing Media: single 5.25-inch floppy disk OS: DOS 3.3 Previous cracks: none Similar cracks: #588 Mathematics Activities Courseware Level 6 #452 Mathematics Activities Courseware Level 4 #450 Sailing Through Story Problems #432 Spanish Grammar Review: Future and Conditional Tenses of Regular and Irregular Verbs ~ Chapter 0 In Which Various Automated Tools Fail In Interesting Ways COPYA no errors, but disk boots DOS then prints "DISK READ ERROR" and halts Locksmith Fast Disk Backup ditto EDD 4 bit copy (no sync, no count) ditto Copy ][+ nibble editor Track $23 appears to contain data, but it has no sector headers or structure. I re-ran EDD4 and copied track $23 this time, and now the copy boots slightly further but exits to a BASIC prompt. Disk Fixer T00 -> DOS 3.3 bootloader / RWTS T00-T02 -> full copy of DOS 3.3 T11 -> DOS 3.3 catalog T01,S07 -> startup program is "MULTIPLOY" Can't find any way to read track $23 Why didn't any of my copies work? Probably a nibble check in the startup program that interprets the specially formatted track $23 during boot just reboots. A well-timed gets me a prompt, but any command reboots again. Next steps: 1. Trace the startup program 2. Disable the nibble check 3. There is no step 3 (I hope) ~ Chapter 1 In Which Things Start Off Poorly And Go Downhill Rapidly Booting from my work disk, the non- working copy ought to have a catalog, but it has suspiciously vanished. [S6,D1=non-working copy from Locksmith] [S5,D1=my work disk] ]PR#5 ... ]CATALOG,S6,D1 C1983 DSR^C#254 401 FREE Turning to my trusty Disk Fixer sector editor, I immediately go to T11,S00 to see if there is a simple fix. And there is... --v-- -------------- DISK EDIT -------------- TRACK $11/SECTOR $00/VOLUME $FE/BYTE$00 --------------------------------------- $00:>04<00 0F 03 00 00 FE 00 D@OC@@~@ $08: 00 00 00 00 00 00 00 00 @@@@@@@@ $10: 00 00 00 00 00 00 00 00 @@@@@@@@ $18: 00 00 00 00 00 00 00 00 @@@@@@@@ $20: 00 00 00 00 00 00 00 7A @@@@@@@: $28: 00 00 00 00 00 00 00 00 @@@@@@@@ $30: 1F 01 00 00 23 10 00 01 _A@@#P@A $38: 00 00 00 00 00 00 00 00 @@@@@@@@ $40: 00 00 00 00 FF FF 00 00 @@@@..@@ $48: FF FF 00 00 FF FF 00 00 ..@@..@@ $50: FF FF 00 00 FF FF 00 00 ..@@..@@ $58: FF FF 00 00 FF FF 00 00 ..@@..@@ $60: FF FF 00 00 FF FF 00 00 ..@@..@@ $68: FF FF 00 00 FF FF 00 00 ..@@..@@ $70: FF FF 00 00 FF FF 00 00 ..@@..@@ $78: FF FF 00 00 00 00 00 00 ..@@@@@@ --------------------------------------- BUFFER 0/SLOT 6/DRIVE 1/MASK OFF/NORMAL --------------------------------------- COMMAND : _ --^-- The DOS on this disk apparently hard- codes the track number, and the disk catalog on track $11 has a bogus track number. (Further inspection of track $11 confirms that there really is a standard disk catalog on the disk. The only problem is that third-party disks can't see it because they trust T11,S00 to tell them where to look first. And that's what you get for trusting untrusted input!) But how does the original disk know where to look? I scoured "Beneath Apple DOS" until I found the answer on p8-28: --v-- B011-B036 Read a directory sector ; (If CARRY flag is zero on entry, read first directory sector. If CARRY is one, read next) ; Memorize entry code. ; Set buffer pointers (B045). ; First or next? ; If first, get track/sector of directory sector from VTOC at offset +1,+2. ; Otherwise, get track/sector from directory sector at offset +1,+2. If track is zero, exit with error code (end of directory). ; Call RWTS to read sector. ; Exit with normal return code. --^-- So, to read the first sector of file names and other metadata, this routine is supposed to look at the VTOC sector buffer (read from T11,S00 and stored at $B3BB..$B4BA). The VTOC says "hey, the first sector of files and stuff is in T11,S0F" so this routine is supposed to read T11,S0F. But the DOS on this disk made one small modification to that routine. (This is on T01,S0F.) B011- 08 PHP B012- 20 45 B0 JSR $B045 B015- 28 PLP B016- B0 08 BCS $B020 B018- AC BD B3 LDY $B3BD ------ B01B- A2 11 LDX #$11 << hey B01D- EA NOP << now ------ B01E- D0 0A BNE $B02A B020- AE BC B4 LDX $B4BC B023- D0 02 BNE $B027 B025- 38 SEC B026- 60 RTS B027- AC BD B4 LDY $B4BD B02A- 8E 97 B3 STX $B397 B02D- 8C 98 B3 STY $B398 B030- A9 01 LDA #$01 B032- 20 52 B0 JSR $B052 B035- 18 CLC B036- 60 RTS Instead of getting the track number from the VTOC, it hard-codes track $11. Now that I've identified the problem, the fix is straightforward. If I change the VTOC header (T11,S00) to point to the actual first directory sector (T11,S0F), DOS 3.3 or any other copy utility should be able to read the disk catalog. T11,S00,$01: 00 -> 11 ]PR#5 ... ]CATALOG,S6,D1 C1983 DSR^C#254 401 FREE B 029 SSPROT$$A A 009 SELECT A 033 MULTIPLY B 002 RESET B 002 SOUND B 003 CHAIN B 010 S-TABLE1 A 002 MULTIPLOY B 005 SSPROT$$1 That's better. ~ Chapter 2 But Wait, It Gets Worse ]LOAD MULTIPLOY,S6,D1 ]LIST 5 POKE 254,128 + ASC ("A") 10 PRINT "BRUN SSPROT$$1" 20 END ]BLOAD SSPROT$$1 ; Diversi-DOS-specific command to get ; the address and length of the last ; file loaded ]PAD A$0800,L$0302 ]CALL -151 *800L 0800- D8 CLD 0801- 20 0B 08 JSR $080B 0804- 4C 00 09 JMP $0900 *80BL ; pop return address and put it in two ; seemingly arbitrary locations 080B- 68 PLA 080C- 8D 01 0B STA $0B01 ; =$03 080F- 68 PLA 0810- 8D 56 08 STA $0856 ; =$08 ; compute a checksum to make sure ; nothing has been modified 0813- A9 00 LDA #$00 0815- AA TAX 0816- 5D 00 08 EOR $0800,X 0819- CA DEX 081A- F0 03 BEQ $081F 081C- 4C 16 08 JMP $0816 ; store the checksum in what was ; executable code just a few cycles ago 081F- 8D 0F 08 STA $080F ; set the BRK vector to reboot 0822- A9 00 LDA #$00 0824- 8D F0 03 STA $03F0 0827- A9 C6 LDA #$C6 0829- 8D F1 03 STA $03F1 ; and now a decryption loop that ; decrypts based on the checksum of the ; code 082C- A9 08 LDA #$08 082E- 85 B0 STA $B0 0830- A0 53 LDY #$53 0832- A9 00 LDA #$00 0834- 85 AF STA $AF 0836- 85 FF STA $FF 0838- A5 FF LDA $FF 083A- 51 AF EOR ($AF),Y 083C- 4D 0F 08 EOR $080F 083F- 91 AF STA ($AF),Y 0841- 45 FF EOR $FF 0843- 85 FF STA $FF 0845- EE 0F 08 INC $080F 0848- C8 INY 0849- D0 ED BNE $0838 084B- E6 B0 INC $B0 084D- A5 B0 LDA $B0 084F- C9 0A CMP #$0A 0851- D0 E5 BNE $0838 ; everything after this is encrypted 0853- 6B ??? 0854- 33 ??? 0855- 3A ??? ; note that this byte in particular was ; modified earlier based on the return ; address on the stack 0856- 30 A0 BMI $07F8 0858- AF ??? 0859- 0E 79 58 ASL $5879 085C- 9E ??? 085D- 9C ??? 085E- 36 37 ROL $37,X 0860- BD 4E 4E LDA $4E4E,X One thing at a time. This routine at $080B is called from $0801, so the top of the stack is going to contain $03, then $08. $03 goes into $0B01 and $08 goes into $0856. Given that, I can reproduce the checksum calculation elsewhere to determine the decryption key that ends up in $080F. *B01:03 ; originally from stack *856:08 ; originally from stack *8000<800.8FFM ; copy everything *801E:80 ; fix in-loop JMP *8022:60 ; stop after checksum *8013G ; calculate checksum *80F ; and the answer is... 080F- 27 *8053:60 ; stop after decryption *802CG ; decrypt *853L 0853- 4C 57 08 JMP $0857 Well, that's something. ~ Chapter 3 In Which We Get The Distinct Impression That We're Swimming Against The Current *857L ; execute an RWTS command with a ; custom parameter table located at ; $0874 0857- A9 08 LDA #$08 0859- A0 74 LDY #$74 085B- 20 D9 03 JSR $03D9 *874 0874- .. .. .. .. 01 60 01 00 0878- 23 00 85 08 00 09 00 00 ^^ track $23 (!) 0880- 00 00 00 60 01 00 01 EF 0888- D8 So we're seeking to track $23. But why? ; set reset vector 085E- A9 00 LDA #$00 0860- 8D F2 03 STA $03F2 0863- A9 C6 LDA #$C6 0865- 8D F3 03 STA $03F3 0868- A9 63 LDA #$63 086A- 8D F4 03 STA $03F4 ; set RUN flag (makes every command ; typed from the BASIC prompt execute ; RUN instead) 086D- A9 80 LDA #$80 086F- 85 D6 STA $D6 0871- 4C 89 08 JMP $0889 *889L ; turn on drive motor (hard-coded to ; slot 6) 0889- A2 60 LDX #$60 088B- BD 8E C0 LDA $C08E,X 088E- BD 8C C0 LDA $C08C,X 0891- BD 8A C0 LDA $C08A,X 0894- BD 89 C0 LDA $C089,X A fun(*) thing to do is boot original floppies from slot 5. Lots of copy protection routines (including this one) hard-code slot 6, so you can find out when they're called because the slot 6 drive light will suddenly go on. (*) not guaranteed, actual fun may vary ; wait loop 0897- A9 AA LDA #$AA 0899- 20 A8 FC JSR $FCA8 089C- A9 40 LDA #$40 089E- 85 FA STA $FA ; skip some nibbles 08A0- BD 8C C0 LDA $C08C,X 08A3- 10 FB BPL $08A0 08A5- C6 F9 DEC $F9 08A7- D0 F7 BNE $08A0 08A9- C6 FA DEC $FA 08AB- D0 F3 BNE $08A0 08DA- EA NOP 08AD- AD A6 09 LDA $09A6 08B0- 8D A7 09 STA $09A7 08B3- 20 3F 09 JSR $093F *93FL ; initialize state 093F- A0 00 LDY #$00 0941- 18 CLC 0942- 2A ROL 0943- 85 FB STA $FB ; look for a specific nibble sequence ; "F7 F6 EF EE" while keeping some sort ; of rolling checksum 0945- AD EC C0 LDA $C0EC 0948- 10 FB BPL $0945 094A- AA TAX 094B- 45 FB EOR $FB 094D- 2A ROL 094E- 49 41 EOR #$41 0950- 85 FB STA $FB ; if Death Counter rolls over to $00 ; (meaning we couldn't find this nibble ; sequence), branch to the failure path ; which turns off the drive motor and ; displays "DISK READ ERROR" message 0952- C8 INY 0953- F0 B3 BEQ $0908 0955- 8A TXA 0956- C9 F7 CMP #$F7 0958- D0 EB BNE $0945 095A- AD EC C0 LDA $C0EC 095D- 10 FB BPL $095A 095F- AA TAX 0960- 45 FB EOR $FB 0962- 2A ROL 0963- 49 41 EOR #$41 0965- 85 FB STA $FB 0967- C8 INY 0968- 8A TXA 0969- C9 F7 CMP #$F7 096B- F0 ED BEQ $095A 096D- C9 F6 CMP #$F6 096F- D0 D4 BNE $0945 0971- AD EC C0 LDA $C0EC 0974- 10 FB BPL $0971 0976- AA TAX 0977- 45 FB EOR $FB 0979- 2A ROL 097A- 49 41 EOR #$41 097C- 85 FB STA $FB 097E- C8 INY 097F- 8A TXA 0980- C9 F7 CMP #$F7 0982- F0 D6 BEQ $095A 0984- C9 EF CMP #$EF 0986- D0 BD BNE $0945 0988- AD EC C0 LDA $C0EC 098B- 10 FB BPL $0988 098D- AA TAX 098E- 45 FB EOR $FB 0990- 2A ROL 0991- 49 41 EOR #$41 0993- 85 FB STA $FB 0995- C8 INY 0996- 8A TXA 0997- C9 F7 CMP #$F7 0999- F0 BF BEQ $095A 099B- C9 EE CMP #$EE 099D- D0 A6 BNE $0945 099F- AD EC C0 LDA $C0EC 09A2- 10 FB BPL $099F 09A4- 60 RTS Continuing from $08B6... ; compare the nibble we just read ; (at $099F) 08B6- C9 AB CMP #$AB ; if it's not $AB, jump to failure path 08B8- D0 4E BNE $0908 08BA- A0 05 LDY #$05 08BC- 20 27 09 JSR $0927 *927L ; match next nibbles against an array ; of $40 known nibbles (starting at ; $09A9 + an initial offset given in Y) ; over and over until we find a nibble ; that doesn't match 0927- A2 00 LDX #$00 0929- AD EC C0 LDA $C0EC 092C- 10 FB BPL $0929 092E- D9 A9 09 CMP $09A9,Y 0931- D0 0A BNE $093D 0933- C8 INY 0934- 98 TYA 0935- 29 3F AND #$3F 0937- A8 TAY 0938- D0 EF BNE $0929 ; count the number of times we "wrapped ; around" to the start of the array 093A- E8 INX 093B- D0 EC BNE $0929 ; the number of times we wrapped around ; is the number of groups of these $40 ; nibbles that we found on the track 093D- 86 F9 STX $F9 This is the nibble array that it's looking for: *9A9.9E8 09A9- .. F7 F6 EF EE AB D5 EA 09B0- F5 FA FD FE FF AD ED AE 09B8- EB AF DF B5 DE B6 DD B7 09C0- DB BA DA BB D7 BD D6 BE 09C8- BF FB BF BE D6 AB D5 EA 09D0- F5 FA FD FE FF BD D7 BB 09D8- DA BA DB B7 DD B6 DE B5 09E0- DF AF EB AE ED AD EE EF 09E8- F6 Then execution continues at $093F, which is the same entry point we called once before, looking for "F7 F6 EF EE". So, continuing from $08BF, after having found "F7 F6 EF EE" for a second time: ; store Y (was used to count the total ; number of nibbles it took to find the ; sequence "F7 F6 EF EE") 08BF- 84 FA STY $FA ; check the last nibble read (at $099F) 08C1- C9 AB CMP #$AB ; wrong nibble --> off to The Badlands 08C3- D0 43 BNE $0908 ; save a copy of the rolling checksum 08C5- A5 FB LDA $FB 08C7- 85 FC STA $FC ; now start over 08C9- A2 00 LDX #$00 08CB- A0 05 LDY #$05 08CD- AD EC C0 LDA $C0EC 08D0- 10 FB BPL $08CD ; go match nibbles and count nibble ; groups again (against the array at ; $099F, same as last time) 08D2- 20 2E 09 JSR $092E ; compare the rolling checksum we got ; this time to the one we got last time ; (but don't actually care if they ; differ -- possibly some options have ; been NOP'd out of a more generalized ; protection routine?) 08D5- A5 FB LDA $FB 08D7- C5 FC CMP $FC 08D9- EA NOP 08DA- EA NOP ; get number of nibble groups we found ; during $092F..0942 (stored in $F9 at ; $093D) 08DB- A5 F9 LDA $F9 08DD- EA NOP 08DE- EA NOP ; These were initialized to be equal ; (at $08B0). $09A7 is never changed, ; so this will check whether this is ; the first time we got to this point. 08DF- AE A7 09 LDX $09A7 08E2- EC A6 09 CPX $09A6 08E5- D0 08 BNE $08EF ; yes, first run -- store the number of ; nibble groups we found 08E7- 8D A8 09 STA $09A8 ; loop back to do everything again ; several times 08EA- CE A7 09 DEC $09A7 08ED- D0 C4 BNE $08B3 ; execution always continues here on ; anything but the first run -- ; check that the total number of ; nibble groups we found is the same as ; it was last time 08EF- 4D A8 09 EOR $09A8 ; if we found a different number of ; nibble groups this time around, jump ; to The Badlands 08F2- D0 14 BNE $0908 ; loop back to do everything again ; several times 08F4- CE A7 09 DEC $09A7 08F7- D0 BA BNE $08B3 ; turn off drive 08F9- AD E8 C0 LDA $C0E8 ; success path falls through to here -- ; take the number of nibble groups we ; found and munge it with the value of ; $0B00 08FC- AD A8 09 LDA $09A8 08FF- 4D 00 0B EOR $0B00 0902- 8D CF 03 STA $03CF 0905- 4C F9 09 JMP $09F9 ; failure path is here -- try a few ; times before giving up completely 0908- CE A5 09 DEC $09A5 090B- D0 A0 BNE $08AD ; (giving up now) turn off drive motor, 090D- AD E8 C0 LDA $C0E8 ; ...clear the screen, 0910- 20 58 FC JSR $FC58 ; ...print an error message, 0913- AD E9 09 LDA $09E9 0916- 29 7F AND #$7F 0918- AA TAX 0919- A0 00 LDY #$00 091B- B9 EA 09 LDA $09EA,Y 091E- 20 ED FD JSR $FDED 0921- C8 INY 0922- CA DEX 0923- D0 F6 BNE $091B ; ...and hang 0925- F0 FE BEQ $0925 *FC58G N 400<9EA.9FFM DISK READ ERROR ...which is exactly the behavior I saw on my non-working copy. ~ Chapter 4 Now It Can Be Told It's clear that this code has been trying to determine if the disk is original, by counting a sequence of nibbles on track $23. But unlike other protection schemes, it is not at all clear WHY this works. What makes this particular nibble sequence so special that it can't be copied with EDD or some other bit copier? To answer this question, we need to do a very deep dive, all the way into the bit copier code itself. Here, in the bowels of EDD (v4.9, but versions 4.0+ share this code), is the routine that reads nibbles from a track while simultaneously checking if each nibble is followed by a timing bit: ; ($00) --> $6000, the buffer to store ; raw nibbles B834- A9 60 LDA #$60 B836- 85 01 STA $01 B838- A0 00 LDY #$00 B83A- 84 00 STY $00 ; All nibbles need to have the high bit ; set, so EDD uses an AND mask of #$7F ; (strip the high bit) if it determines ; that a nibble is followed by a timing ; bit. B83C- A9 7F LDA #$7F B83E- 85 02 STA $02 ; slot number (x16) B840- A6 10 LDX $10 ; Needless to say, this code is the ; epitome of cycle-counting, so every ; instruction matters, even if it does ; nothing but burn cycles. B842- EA NOP B843- 85 03 STA $03 B845- EA NOP B846- EA NOP ; The normal "LDA $C08C,X / BPL" loop ; is unrolled here. EDD tries to read ; the nibble value itself and detect ; whether a timing bit exists after it. B847- BD 8C C0 LDA $C08C,X B84A- 30 1B BMI $B867 B84C- BD 8C C0 LDA $C08C,X B84F- 30 16 BMI $B867 ; timing bit probably present B851- BD 8C C0 LDA $C08C,X B854- 30 1B BMI $B871 B856- BD 8C C0 LDA $C08C,X B859- 30 16 BMI $B871 B85B- BD 8C C0 LDA $C08C,X B85E- 30 11 BMI $B871 B860- BD 8C C0 LDA $C08C,X B863- 30 0C BMI $B871 ; 3-cycle penalty if branch is taken! ; BTW, this is an unconditional branch, ; since the instruction before this ; was a BMI, and every value is either ; minus or plus. B865- 10 E5 BPL $B84C ; Execution continues here from $B84A ; or $B84F. No timing bit was detected, ; so store the nibble and move on. B867- 91 00 STA ($00),Y B869- C8 INY B86A- D0 D6 BNE $B842 B86C- E6 01 INC $01 B86E- 10 D6 BPL $B846 B870- 60 RTS ; Execution continues here from $B854, ; $B859, $B85E, or $B863. A timing bit ; was detected, so apply the AND mask ; to indicate this. (The disk write ; routine will check this later.) B871- 25 02 AND $02 B873- 91 00 STA ($00),Y B875- C8 INY ; Unfortunately for EDD, branching here ; and applying the AND mask requires ; enough CPU cycles that we will miss ; one bit on disk by the time we branch ; back and start looking at nibble ; values again. This is usually not a ; problem, except when it is... B876- D0 CE BNE $B846 B878- E6 01 INC $01 B87A- 10 D0 BPL $B84C B87C- 60 RTS Normally, a nibble will be shifted in before the unrolled loop gets very far, so execution branches to $B867 and the nibble is stored intact. However, because EDD only checks the data latch six times, this nibble read routine is vulnerable to a well-placed timing bit, such that the "BPL" at $B865 will be reached just before the last bit of the nibble is shifted in. That 3-cycle time penalty when the branch is taken is just enough that, when combined with the 2-cycle instruction before it, the shift will complete, and the four CPU cycles will elapse, before the next read occurs. The result is that EDD gets "out of phase" with the proper start of the nibbles, and the next few nibbles that arrive will mistakenly branch to $B871 instead of $B867, losing one bit each. When those data are written to disk by the bit-copier, the values will be entirely wrong. Now imagine an entire track that is full of repeated sequences. Each of the sequences has a prologue, five nibbles in length. Every other prologue has a timing bit after each nibble. In the middle of the track is a collection of nibbles which do not match the sequence, so the entire track is split into two identical groups. When EDD attempts to read the track, it misses a crucial timing bit, gets "out of phase," and ends up misreading about half of the sequences on the track. It misreads the rest of the nibble groups in the first half of the track, gets back in sync with the nibbles in the middle, then misreads the second half the same way. The equality check at $08EF passes, but the total count of nibble groups is much lower than the original, because the entire structure of the track exploits this design weakness in EDD. Fun fact(*): Copy II+'s nibble-and- timing-bit-reading code is so similar to EDD's that it shares the same design weakness and is also defeated by this protection scheme. (*) not guaranteed, actual fun may vary ~ Chapter 5 Just Keep Swimming, Just Keep Swimming I need this protection check to pass at least once, because I need to know the number of nibble groups on the original disk. Thus: *905:60 ; halt after protection check *857G ; run check from beginning ; (includes disk seek to T23) *9A8 09A8- 61 $61 nibble groups. (The value of $09A8 on my failed EDD4 bit copy was $01! It misread basically the entire track.) *3CF 03CF- 01 OK then. Now we can continue at $09F9. *9F9L 09F9- A2 00 LDX #$00 09FB- BD 09 0A LDA $0A09,X 09FE- 9D 00 03 STA $0300,X 0A01- E8 INX 0A02- E0 79 CPX #$79 0A04- D0 F5 BNE $09FB 0A06- 4C 00 03 JMP $0300 *A06:60 ; pause again *9F9G *300L ; take the value that was POKEd by the ; HELLO program, back in the beginning 0300- A5 FE LDA $FE ; and store it later in this code 0302- 8D 76 03 STA $0376 ; don't know what this does yet 0305- A2 68 LDX #$68 0307- 20 5C 03 JSR $035C *35CL ; ah, it's executing a DOS command by ; printing a Ctrl-D followed by a ; string 035C- A9 84 LDA #$84 035E- 20 ED FD JSR $FDED 0361- E8 INX 0362- BD FF 02 LDA $02FF,X 0365- D0 F7 BNE $035E 0367- 60 RTS *FC58G N 400<368.378M BLOAD SSPROT$$AM@ The last two characters are a carriage return and a null. (They're displayed in inverse on a real machine; sorry that doesn't translate well to text.) The character before that was set at $0302 from the value POKEd by the HELLO program. So many layers. This loads another file into memory by a standard BLOAD command, so I'll reproduce that. *BLOAD SSPROT$$A Then it just returns to the caller, so let's continue the listing from there. ; oh look, another decryption loop 030A- A9 08 LDA #$08 030C- 85 68 STA $68 030E- A9 01 LDA #$01 0310- 85 67 STA $67 0312- AD FF 07 LDA $07FF 0315- 85 B0 STA $B0 0317- AC FE 07 LDY $07FE 031A- A9 00 LDA #$00 031C- 85 AF STA $AF 031E- 85 FF STA $FF 0320- A5 FF LDA $FF 0322- 51 AF EOR ($AF),Y ; oh look, the decryption key is the ; value we derived from the number of ; nibble groups we counted on track $23 0324- 4D CF 03 EOR $03CF 0327- 91 AF STA ($AF),Y 0329- 45 FF EOR $FF 032B- 85 FF STA $FF 032D- EE CF 03 INC $03CF 0330- 88 DEY 0331- C0 FF CPY #$FF 0333- D0 EB BNE $0320 0335- C6 B0 DEC $B0 0337- A5 B0 LDA $B0 0339- C9 07 CMP #$07 033B- D0 E3 BNE $0320 ; now setting up a bunch of... ; Applesoft BASIC zero page globals??? 033D- AD FE 07 LDA $07FE 0340- 85 69 STA $69 0342- 85 6B STA $6B 0344- 85 6D STA $6D 0346- 85 AF STA $AF 0348- AD FF 07 LDA $07FF 034B- 85 6A STA $6A 034D- 85 6C STA $6C 034F- 85 6E STA $6E 0351- 85 B0 STA $B0 ; setting up the indirect JMP from the ; warm-start vector at $03D0 0353- AD D2 03 LDA $03D2 0356- 8D 5B 03 STA $035B ; and running the BASIC program in ; memory 0359- 6C 58 9D JMP ($9D58) Wait, what BASIC program in memory? The one we just decrypted, of course. The BLOAD SSPROT$$A command loaded an encrypted BASIC program, then decrypted it in place. Then this runs it, the hard way. (This explains why my final EDD4 bit copy failed. It's decrypting a BASIC program with the wrong key in $03CF!) It looks like the decryption loop ends at $033D, but setting the Applesoft globals is also important, because it tells BASIC how large the program is. Remember, we're completely subverting the usual mechanisms here, so we have to do everything manually. So let's let it do all that, then drop back to BASIC and take a look. *353:4C D0 03 ; warm-start DOS after ; decryption loop and ; setting up Applesoft *30AG ; do the decryption ]LIST 1 ONERR GOTO 5 3 GOTO 10 5 CALL - 10621 7 POKE 216,0 9 GOTO 1 10 PRINT CHR$ (13) + CHR$ (4) ;"BLOAD RESET" 12 CALL 24610 15 REM ==================== 20 REM == MULTIPLOY == 30 REM == == 40 REM == PAUL COLETTA == 50 REM == 1981 == 60 REM == == 70 REM ==================== 80 REM 81 CLEAR 82 DIM RR(4),M1(4,5),M2(4,5) 84 DIM M3(4,5),M4(4,5),S(4,9),D (4,9) 88 DIM X(10),Y(10),XX(10),YY(10 . . . Well, would you look at that. It's a perfectly normal BASIC program. Now to move the old startup program out of the way and save the newly decrypted startup program in its place. ]RENAME MULTIPLOY,OLD MULTIPLOY ]SAVE MULTIPLOY ]PR#6 ...works... Quod erat liberandum. ~ Acknowledgements The explanation of this copy protection scheme was first published by qkumba in PoC||GTFO 0x10, currently available at https://www.alchemistowl.org/pocorgtfo/ pocorgtfo10.pdf --------------------------------------- A 4am crack No. 1193 ------------------EOF------------------