-----Ulysses and the Golden Fleece----- A 4am crack 2020-02-03 --------------------------------------- Name: Ulysses and the Golden Fleece Version: 1.1 Genre: adventure Year: 1982 Credits: Bob Davis, Ken Williams Publisher: On-Line Systems Platform: Apple ][+ or later Media: 5.25-inch disk Sides: 2 OS: DOS 3.3 Previous cracks: none (of this version) ~ Chapter 0 In Which Various Automated Tools Fail In Interesting Ways COPYA no errors, but copy boots to title screen then fills screen with garbage and reboots Locksmith Fast Disk Backup ditto EDD 4 bit copy (no sync, no count) works Copy ][+ nibble editor looks like a normal disk, which makes sense since COPYA thought it copied it Disk Fixer normal bootloader and DOS 3.3 normal disk catalog on track $11 with oddly named files, but real Why didn't COPYA work? presumably some runtime protection check that is looking for nibbles between the sectors, or properties of nibbles that COPYA doesn't maintain Next steps: 1. Load startup program 2. Disable the protection check 3. Declary victory(*) (*) go to the gym ~ Chapter 1 Negative, I Am A Meat Popsicle [S6,D1=non-working copy] [S5,D1=my work disk] ]PR#5 ... ]CATALOG,S6,D1 C1983 DSR^C#254 470 FREE ULYSSES (C) 1982 ON-LINE SYSTEMS WRITTEN BY BOB DAVIS AND KEN WILLIAMS DOS 3.3 filename tricks feel very 1982. Inspecting the disk with Copy ][+, I can see that those are, in fact, real files which contain Ctrl-H characters to make them print over the usual file metadata in the disk catalog. It also makes them difficult to load manually. Let's fix that (ON A COPY OF COURSE). [Copy ][+] [RENAME] [FILES] [SLOT 6, DRIVE 1] rename the first file to HELLO [CHANGE BOOT PROGRAM} [SLOT 6, DRIVE 1] change boot program to HELLO Rebooting the disk confirms that it still boots as far as it did last time, so let's go. ]PR#5 ... ]CATALOG,S6,D1 C1983 DSR^C#254 470 FREE B 004 HELLO ON-LINE SYSTEMS WRITTEN BY BOB DAVIS AND KEN WILLIAMS ]BLOAD HELLO ; this is a command available only in ; the 64K version of Diversi-DOS, ; which easily prints the address and ; length of the last file you BLOADed ]PAD A$2515,L$03F0 ]CALL -151 *2515L ; prints the title screen (not shown, ; but I can literally type "26A9G" at ; this point and it displays the text ; and returns safely to the monitor) 2515- 20 A9 26 JSR $26A9 ; copy some code to $300 (not shown) 2518- 20 33 26 JSR $2633 ; set up RWTS parameters ; track 4 251B- A9 04 LDA #$04 251D- 8D EC B7 STA $B7EC ; disk volume 0 (wildcard) 2520- A9 00 LDA #$00 2522- 8D EB B7 STA $B7EB ; read into $3000 2525- A9 00 LDA #$00 2527- 8D F0 B7 STA $B7F0 252A- A9 30 LDA #$30 252C- 8D F1 B7 STA $B7F1 ; reads #$65 sectors into $3000+ (not ; shown) 252F- A9 65 LDA #$65 2531- 20 67 25 JSR $2567 That's about as far as my non-working copy gets before rebooting, so we're getting close to the part that goes boom. 2534- 20 44 26 JSR $2644 *2644L ; wipe language card 2644- AD 83 C0 LDA $C083 2647- AD 83 C0 LDA $C083 264A- A0 00 LDY #$00 264C- 84 10 STY $10 264E- A9 D0 LDA #$D0 2650- 85 11 STA $11 2652- A9 12 LDA #$12 2654- 91 10 STA ($10),Y 2656- C8 INY 2657- D0 FB BNE $2654 2659- E6 11 INC $11 265B- D0 F7 BNE $2654 265D- AD E9 B7 LDA $B7E9 2660- 4A LSR 2661- 4A LSR 2662- 4A LSR 2663- 4A LSR 2664- 09 C0 ORA #$C0 ; set reset vectors in language card ; and page 3 2666- 8D FD FF STA $FFFD 2669- 8C FC FF STY $FFFC 266C- 8C F2 03 STY $03F2 266F- 8D F3 03 STA $03F3 2672- 49 A5 EOR #$A5 2674- 8D F4 03 STA $03F4 2677- AD 81 C0 LDA $C081 ; reset I/O vectors 267A- A9 F0 LDA #$F0 267C- 85 36 STA $36 267E- A9 FD LDA #$FD 2680- 85 37 STA $37 2682- 85 39 STA $39 2684- A9 1B LDA #$1B 2686- 85 38 STA $38 2688- 60 RTS So close, I can smell it. ~ Chapter 2 Anyone else want to negotiate? Continuing from $2537... ; save current track on the stack 2537- AD EC B7 LDA $B7EC 253A- 48 PHA ; do a thing 253B- 20 B3 25 JSR $25B3 ; restore track 253E- 68 PLA 253F- 8D EC B7 STA $B7EC Well THAT'S totally suspicious. Let's see what's at $25B3. *25B3L ; compute a checksum of $2700-$27FF 25B3- A9 00 LDA #$00 25B5- A8 TAY 25B6- 59 00 27 EOR $2700,Y 25B9- C8 INY 25BA- D0 FA BNE $25B6 ; save it in zero page 25BC- 85 10 STA $10 ; always branches, because the INY set ; the zero flag which exited the loop ; (this is unrelated to the value of ; the checksum we just stored) 25BE- F0 01 BEQ $25C1 ; this is bogus code, because we just ; branched into the middle of it 25C0- A9 A9 LDA #$A9 25C2- 20 59 00 JSR $0059 So this is fun: branching forward 1 instruction, so if you don't notice, you start seeing garbage and don't know why. Execution actually continues at $25C1. *25C1L ; calculate another checksum, but only ; every other byte 25C1- A9 20 LDA #$20 25C3- 59 00 27 EOR $2700,Y 25C6- C8 INY 25C7- C8 INY 25C8- D0 F9 BNE $25C3 ; and store that in zero page 25CA- 85 11 STA $11 ; perturb both values to create an ; address, and push it to the stack 25CC- 49 B7 EOR #$B7 25CE- 48 PHA 25CF- A5 10 LDA $10 25D1- 49 11 EOR #$11 25D3- 48 PHA 25D4- D0 01 BNE $25D7 ; bogus, because we branched into the ; middle of it 25D6- 4C 60 08 JMP $0860 *25D7L ; "return" to the address we just ; created and pushed to the stack 25D7- 60 RTS OK, I don't know where this returns to, because it's calculated based on the checksums and EOR instructions. So I get to calculate it myself. Some minor modifications of this code: ; store high byte of calculated address 25CE- 8D 01 03 STA $0301 25D1- A5 10 LDA $10 25D3- 49 11 EOR #$11 ; store low byte 25D5- 8D 00 03 STA $0300 ; actually return 25D8- 60 RTS *25B3G *10.11 0010- EE 91 The magic zero page values are $EE and $91. *300.301 0300- FF 26 The calculated address is $26FF, so execution continues at $2700 -- the page we just checksummed. ~ Chapter 3 You wanna play it soft, We'll play it soft. You wanna play it hard, Let's play it hard. *2700L 2700- CE 03 27 DEC $2703 2703- EF ??? 2704- 03 ??? 2705- 27 ??? Oh fantastic. Self-modifying the next instruction, then falling through to execute it. OK, I'm going to build a decryption program at a separate address. ; reproduce code at $2700 0300- CE 03 27 DEC $2703 0303- 60 RTS ; save initial code state *1700<2700.27FFM ; decrypt *300G *2700L 2700- CE 03 27 DEC $2703 2703- EE 03 27 INC $2703 2706- AD 24 27 LDA $2724 2709- 49 8A EOR #$8A 270B- D0 01 BNE $270E 270D- 20 8D 24 JSR $248D 2710- 27 ??? Progress! But of course, $2703 is more self-modifying code, and the listing of $270D is bogus because $270B branched into the middle of it (to $270E). *270EL 270E- 8D 24 27 STA $2724 2711- D0 01 BNE $2714 ; bogus 2713- 4C A0 25 JMP $25A0 I'm tired. *2714L 2714- A0 25 LDY #$25 2716- 98 TYA 2717- 59 00 27 EOR $2700,Y 271A- 99 00 27 STA $2700,Y 271D- C8 INY 271E- D0 F6 BNE $2716 2720- 88 DEY 2721- 30 01 BMI $2724 ; bogus 2723- 4C 60 ED JMP $ED60 So tired. *2724L Wait, no, this address was modified earlier (at $270E). So I get to update my decryption program at $300 first. 0300- CE 03 27 DEC $2703 0303- EE 03 27 INC $2703 0306- AD 24 27 LDA $2724 0309- 49 8A EOR #$8A 030B- 8D 24 27 STA $2724 030E- A0 25 LDY #$25 0310- 98 TYA 0311- 59 00 27 EOR $2700,Y 0314- 99 00 27 STA $2700,Y 0317- C8 INY 0318- D0 F6 BNE $0310 031A- 60 RTS ; restore code *2700<1700.17FFM ; decrypt *300G *2724L ; self-modified to fall through here 2724- EA NOP 2725- C8 INY ; Y becomes 0, so this is an RWTS seek 2726- 8C F4 B7 STY $B7F4 ; track 0 2729- 8C EC B7 STY $B7EC ; seek there 272C- A9 B7 LDA #$B7 272E- A0 E8 LDY #$E8 2730- 20 D9 03 JSR $03D9 ; turn on drive motor manually 2733- AE E9 B7 LDX $B7E9 2736- BD 89 C0 LDA $C089,X ; maybe a counter of some kind? or an ; in-game side effect? 2739- A9 03 LDA #$03 273B- 8D FD 95 STA $95FD 273E- 20 90 27 JSR $2790 *2790L ; don't know 2790- A9 1C LDA #$1C 2792- 8D FF 95 STA $95FF ; reset data latch 2795- BD 8E C0 LDA $C08E,X ; find a $D5 nibble 2798- BD 8C C0 LDA $C08C,X 279B- 10 FB BPL $2798 279D- C9 D5 CMP #$D5 279F- EA NOP 27A0- EA NOP 27A1- F0 0F BEQ $27B2 ; decrement Death Counter 27A3- CE FE 95 DEC $95FE 27A6- D0 F0 BNE $2798 27A8- CE FF 95 DEC $95FF 27AB- D0 EB BNE $2798 ; if we never find a $D5 nibble, pop ; the return address and jump to The ; Badlands 27AD- 68 PLA 27AE- 68 PLA 27AF- 4C 63 27 JMP $2763 ; find $AA $96 27B2- BD 8C C0 LDA $C08C,X 27B5- 10 FB BPL $27B2 27B7- C9 AA CMP #$AA 27B9- D0 E2 BNE $279D 27BB- 48 PHA 27BC- 68 PLA 27BD- BD 8C C0 LDA $C08C,X 27C0- 10 FB BPL $27BD 27C2- C9 96 CMP #$96 27C4- D0 F1 BNE $27B7 ; skip 5 nibbles 27C6- A0 05 LDY #$05 27C8- 20 F5 27 JSR $27F5 27CB- C9 AA CMP #$AA 27CD- D0 C9 BNE $2798 ; find $AA nibble 27CF- BD 8C C0 LDA $C08C,X 27D2- 10 FB BPL $27CF 27D4- C9 AA CMP #$AA 27D6- D0 C0 BNE $2798 27D8- 48 PHA 27D9- 68 PLA ; find $D5 nibble 27DA- BD 8C C0 LDA $C08C,X 27DD- 10 FB BPL $27DA 27DF- C9 D5 CMP #$D5 27E1- D0 F7 BNE $27DA 27E3- EA NOP ; find $AA nibble 27E4- BD 8C C0 LDA $C08C,X 27E7- 10 FB BPL $27E4 27E9- C9 AA CMP #$AA 27EB- D0 F2 BNE $27DF 27ED- EA NOP ; skip $15B nibbles 27EE- A0 00 LDY #$00 27F0- 20 F5 27 JSR $27F5 27F3- A0 5B LDY #$5B 27F5- BD 8C C0 LDA $C08C,X 27F8- 10 FB BPL $27F5 27FA- 48 PHA 27FB- 68 PLA 27FC- 88 DEY 27FD- D0 F6 BNE $27F5 27FF- 60 RTS Continuing from $2741... ; count until we find an $FF nibble ; (note: no BPL loop here, so this ; "count" will be sensitive to hidden ; timing bits between nibbles) 2741- A0 00 LDY #$00 2743- BD 8C C0 LDA $C08C,X 2746- C8 INY 2747- C9 FF CMP #$FF 2749- D0 F8 BNE $2743 274B- BD 8C C0 LDA $C08C,X 274E- C8 INY 274F- F0 12 BEQ $2763 ; next nibble must be $C9 2751- C9 C9 CMP #$C9 2753- D0 F6 BNE $274B ; and the "count" must be in a specific ; range 2755- C0 23 CPY #$23 2757- 90 0A BCC $2763 2759- C0 29 CPY #$29 275B- B0 06 BCS $2763 ; if all goes well, turn off the drive ; motor and jump back to $2700 275D- BD 88 C0 LDA $C088,X 2760- 4C 00 27 JMP $2700 The second time around, the decryption loop at $2714 becomes an encryption loop, re-encrypting the protection code in memory. Also, the re-encryption of $2724 takes it from #$EA back to #$60, which means the routine returns to the caller after re-encrypting itself. Which is a neat trick, to be honest. ~ Chapter 4 Multipass This protection check doesn't actually do anything, so on first glance you might thing you could just NOP out the call altogether. Hahahahahahahahahahaha no you can't do that. Why not? Because the checksum values in zero page ($10 and $11) are later used as decryption keys for the game code. Looking back at the initial caller, we see this code: ; save current track 2537- AD EC B7 LDA $B7EC 253A- 48 PHA ; calculate checksums and call ; protection check (will only return if ; check was successful) 253B- 20 B3 25 JSR $25B3 ; restore current track 253E- 68 PLA 253F- 8D EC B7 STA $B7EC ; load some more game code (not shown) 2542- EE EC B7 INC $B7EC 2545- A9 00 LDA #$00 2547- 8D F0 B7 STA $B7F0 254A- A9 09 LDA #$09 254C- 8D F1 B7 STA $B7F1 254F- A9 17 LDA #$17 2551- 20 67 25 JSR $2567 2554- 20 CA 26 JSR $26CA ; and now this 2557- 20 DE 25 JSR $25DE *25DEL 25DE- A0 00 LDY #$00 25E0- B9 18 3E LDA $3E18,Y 25E3- 45 10 EOR $10 <-- ! 25E5- 99 18 3E STA $3E18,Y 25E8- B9 43 09 LDA $0943,Y 25EB- 45 11 EOR $11 <-- ! 25ED- 99 43 09 STA $0943,Y 25F0- C8 INY 25F1- D0 ED BNE $25E0 So that's great. On the bright side, those appear to be the only side effects that matter. The various counters at $95FD-$95FF are never checked or used for anything. I can bypass this protection as long as I set zero page $10 and $11 to the proper values, so the game code is properly decrypted. T17,S0E,$26 -> A9 EE 85 10 A9 91 85 11 2C which turns this: 2537- AD EC B7 LDA $B7EC 253A- 48 PHA 253B- 20 B3 25 JSR $25B3 253E- 68 PLA 253F- 8D EC B7 STA $B7EC into this: 2537- A9 EE LDA #$EE 2539- 85 10 STA $10 253B- A9 91 LDA #$91 253D- 85 11 STA $11 253F- 2C EC B7 BIT $B7EC I played through the entire game, and this crack is complete. Quod erat liberandum. --------------------------------------- A 4am crack No. 2153 ------------------EOF------------------