--------------Wortgefecht-------------- A 4am crack 2014-11-21 --------------------------------------- "Wortgefecht (Word Attack German)" is a 1984 educational game programmed by Seth Meyers and distributed by Gessler Educational Software. It is adapted from "Word Attack" by Richard Eckert and Janice Davidson. The program comes on 2 single-sided disks, labeled "program disk" and "data disk". COPYA fails almost immediately on disk 1. (Disk 2 seems to work OK.) Locksmith Fast Disk Backup copies all tracks except track 3, which seems to be completely unreadable. EDD 4 bit copy gives no read errors, but the copy does not work. It boots, loads DOS from tracks 0-2, runs a startup program of some kind, then clears the screen and prints "DISK READ ERROR" and halts. at the BASIC prompt just reboots. A well-timed gets me a prompt, but any command just reboots. Booting from a 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 184 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:>00<00 0F 03 00 00 FE 00 @@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: 0B FF 00 00 23 10 00 01 K.@@#P@A $38: 00 00 00 00 00 00 00 00 @@@@@@@@ $40: 00 00 00 00 00 00 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 1F FF 00 00 ..@@_.@@ $68: FF FF 00 00 07 FF 00 00 ..@@G.@@ $70: 3F FF 00 00 1F FF 00 00 ?.@@_.@@ $78: 00 03 00 00 00 00 00 00 @C@@@@@@ --------------------------------------- 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.) 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 03 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 change 00 to 11 ]PR#5 ... ]CATALOG,S6,D1 C1983 DSR^C#254 184 FREE A 060 GERMAN HELLO B 003 SSPROT$$A A 030 GERMAN PART 1 A 003 WORD ATTACK! ENDING A 068 GERMAN PART 2 A 043 GERMAN EDITOR A 011 GERMAN INTRODUCTION B 034 GES.PG2 B 034 GES.PG1 A 002 HELLO B 005 SSPROT$$1 T 003 START There we go. ]LOAD HELLO ]LIST 5 POKE 254,128 + ASC ("A") 10 PRINT "BRUN SSPROT$$1" 20 END ]BLOAD SSPROT$$1 ]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 ; gets $03 080C- 8D 01 0B STA $0B01 080F- 68 PLA ; gets $08 0810- 8D 56 08 STA $0856 ; computing 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 ; and storing the checksum in what was ; executable code just a few cycles ago 081F- 8D 0F 08 STA $080F ; setting 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- 53 ??? 0857- A0 AF LDY #$AF 0859- 0E 79 58 ASL $5879 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 ; 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.889 0874- 01 60 01 00 0878- 03 00 85 08 00 09 00 00 ^^ track $03 0880- 00 00 00 60 01 00 01 EF 0888- D8 Aha, we're seeking to the unreadable track ($03). Probably setting up for a nibble check on the raw data there. ; 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 ; here we go -- a classic nibble check ; on the unreadable track ($03) 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 ; turning on the drive motor manually 0894- BD 89 C0 LDA $C089,X ; 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 08AD- AD AC 09 LDA $09AC 08B0- 8D AD 09 STA $09AD ; This subroutine is apparently a ; complicated checksum of raw nibbles. ; I don't really care about the details ; since my goal here is to determine ; how to bypass the entire thing. If ; I actually need the checksum value, ; I can trace it off the original disk. 08B3- 20 45 09 JSR $0945 08B6- C9 AB CMP #$AB 08B8- D0 54 BNE $090E 08BA- A0 05 LDY #$05 08BC- 20 2D 09 JSR $092D 08BF- 84 FA STY $FA 08C1- C9 AB CMP #$AB 08C3- D0 49 BNE $090E 08C5- A5 FB LDA $FB 08C7- 85 FC STA $FC 08C9- A2 00 LDX #$00 08CB- A0 05 LDY #$05 ; now looking for a specific sequence ; of nibbles (still don't care) 08CD- AD EC C0 LDA $C0EC 08D0- 10 FB BPL $08CD 08D2- 20 34 09 JSR $0934 08D5- A5 FB LDA $FB 08D7- C5 FC CMP $FC 08D9- EA NOP 08DA- EA NOP 08DB- A5 F9 LDA $F9 08DD- EA NOP 08DE- EA NOP 08DF- AE AD 09 LDX $09AD 08E2- EC AC 09 CPX $09AC 08E5- D0 08 BNE $08EF 08E7- 8D AE 09 STA $09AE 08EA- CE AD 09 DEC $09AD 08ED- D0 C4 BNE $08B3 08EF- 4D AE 09 EOR $09AE 08F2- D0 1A BNE $090E 08F4- CE AD 09 DEC $09AD 08F7- D0 BA BNE $08B3 08F9- AD E8 C0 LDA $C0E8 08FC- AD AE 09 LDA $09AE ; ultimate checksum 08FF- C9 5C CMP #$5C 0901- 90 0B BCC $090E ; success path is here ; I did not find any prior references ; to $0B00, so I believe it would be ; safe to jump straight to here to ; bypass the nibble check. 0903- A9 60 LDA #$60 0905- 4D 00 0B EOR $0B00 0908- 8D CF 03 STA $03CF 090B- 4C FF 09 JMP $09FF ; failure path is here -- try a few ; times before giving up 090E- CE AB 09 DEC $09AB 0911- D0 9A BNE $08AD ; give up -- turn off drive motor 0913- AD E8 C0 LDA $C0E8 ; clear the screen 0916- 20 58 FC JSR $FC58 ; print an error message 0919- AD EF 09 LDA $09EF 091C- 29 7F AND #$7F 091E- AA TAX 091F- A0 00 LDY #$00 0921- B9 F0 09 LDA $09F0,Y 0924- 20 ED FD JSR $FDED 0927- C8 INY 0928- CA DEX 0929- D0 F6 BNE $0921 ; and hang 092B- F0 FE BEQ $092B *FC58G N 400<9F0.9FEM DISK READ ERROR ...which is exactly the behavior I saw on my non-working copy. Now to continue (manually) on the success path at $0903. Whatever needs to end up in $03CF is probably important later, so let's pause right after that. *90B:60 *903G *9FFL 09FF- A2 00 LDX #$00 0A01- BD 0F 0A LDA $0A0F,X 0A04- 9D 00 03 STA $0300,X 0A07- E8 INX 0A08- E0 79 CPX #$79 0A0A- D0 F5 BNE $0A01 0A0C- 4C 00 03 JMP $0300 *A0C:60 ; pause again *9FFG *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. 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 goody, 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 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. Now this runs it. The hard way! It looks like the decryption loop ends at $033D, so let's pause there and see what's what. *33D:60 *30AG * ]LIST 10 POKE 1012,0 15 TEXT : NORMAL 20 HOME :D$ = CHR$ (13) + CHR$ (4) 30 VTAB 12: PRINT TAB( 10)"ONE MOMENT PLEASE..." 60 PRINT D$;"BLOAD GES.PG1,A$20 00" 65 POKE - 16297,0: POKE - 163 00,0: POKE - 16301,0: POKE - 16304,0 70 PRINT D$;"BLOAD GES.PG2,A$40 00" 75 POKE - 16302,0: POKE - 162 99,0: FOR X = 1 TO 100: IF PEEK ( - 16384) > 127 THEN 90 78 NEXT X 80 POKE - 16302,0: POKE - 163 00,0: FOR X = 1 TO 300 85 NEXT X: GOTO 75 90 HOME : TEXT 93 VTAB 12: PRINT TAB( 10)"ONE MOMENT PLEASE..." 95 PRINT D$;"RUN GERMAN HELLO" Well would you look at that. It's a perfectly normal HELLO program. ]CALL -151 *33D:AD ; restore original code *33DL 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 0353- AD D2 03 LDA $03D2 0356- 8D 5B 03 STA $035B 0359- 6C 58 9D JMP ($9D58) OK, one small problem -- this crashes if I've booted from my work disk, because my work disk uses Diversi-DOS 64K, so the warm-start vector at $3D0 doesn't actually point to anything in the $9Dxx range. It points to an address somewhere in $BFxx instead, but $BF58 is most decidedly *not* the entry point to run a BASIC program. I'm guessing this last bit of self- modifying code at $0353 is meant to compensate for very old computers with less than 64K of RAM. Since the rest of the DOS on the original disk assumes it loads DOS at $9D00..$BFFF, I think it's safe to ignore this and assume the vector at $9D58 is always going to be at $9D58. *356:2C ; change STA to BIT *33DG And the program loads and runs without complaint. Now, how can I permanently patch this to bypass the parts I need to bypass but retain the bits I need to retain? Actually, I'm thinking I should reverse the question: if I start from scratch, which bits do I really need? The bits I need to retain are 1. loading a BASIC program 2. setting a bunch of Applesoft globals to non-standard values 3. running the BASIC program Everything else is nonsense. Nibble checks, checksums, decryption loops, the entire DOS on the original disk... all nonsense. (It ran from Diversi-DOS from my work disk, so it can't possibly be relying on anything from the original DOS.) [...redo everything up to actually running the program at $033D...] *2800<800.1FFFM ; save decrypted HELLO *C500G ; reboot to work disk ... ]CALL -151 *800<2800.3FFFM ; restore HELLO * Now I have the *real* hello program in memory, and I can save it as a regular BASIC program, on my non-working copy. ]RENAME HELLO,OLD HELLO,S6,D1 ]SAVE HELLO,S6,D1 I only need a small boot program that sets the Applesoft zero page globals then runs the HELLO program I just saved. ]CALL -151 0300- A9 CB LDA #$66 0302- 85 69 STA $69 0304- 85 6B STA $6B 0306- 85 6D STA $6D 0308- 85 AF STA $AF 030A- A9 0A LDA #$09 030C- 85 6A STA $6A 030E- 85 6C STA $6C 0310- 85 6E STA $6E 0312- 85 B0 STA $B0 0314- A2 00 LDX #$00 0316- BD 20 03 LDA $0320,X 0319- 20 ED FD JSR $FDED 031C- E8 INX 031D- 4C 16 03 JMP $0316 ; Ctrl-D + "RUN HELLO" + Ctrl-M + $00 0320- 84 D2 D5 CE A0 C8 C5 CC 0328- CC CF 8D 00 *BSAVE BOOT,A$300,L$7F,S6,D1 Using Copy ][+, I can "copy DOS" from a freshly initialized DOS 3.3 disk onto the non-working copy. This function of Copy ][+ just sector-copies tracks 0-2 from one disk to another, but it's easier than setting that up manually in some other copy program. Copy ][+ --> COPY --> DOS --> from slot 6, drive 2 --> to slot 6, drive 1 [S6,D1=non-working copy] [S6,D2=newly formatted DOS 3.3 disk] ...read read read... ...write write write... And finally, I need to change the startup program from "HELLO" to "BOOT". Copy ][+ has a dedicated function for this, too. It presents a list interface to choose a file from the catalog, then sector-edits track 1, sector 9 to set the name of the program that DOS runs (instead of "HELLO"). It also properly sets the bits elsewhere in DOS to tell it that the startup program is a binary file. Copy ][+ --> CHANGE BOOT PROGRAM --> on slot 6, drive 1 --> BOOT ]PR#6 The program boots and runs without complaint. There doesn't appear to be any further copy protection. Quod erat liberandum. --------------------------------------- A 4am crack No. 169 ------------------EOF------------------