-------------Zykron Hearts------------- A 4am crack 2018-07-05 -------------------. updated 2019-03-26 |___________________ Name: Zykron Hearts Genre: games/card Year: 1986 Publisher: Zykron Company Platform: Apple ][+ or later Media: 5.25-inch disk Sides: 1 OS: DOS 3.3 Previous cracks: none ~ Chapter 0 In Which Various Automated Tools Fail In Interesting Ways COPYA fails on first pass Locksmith Fast Disk Backup unable to read track 4 or 5 copy boots DOS then crashes EDD 4 bit copy (no sync, no count) no errors, but copy still boots DOS then crashes Copy ][+ nibble editor tracks 4 and 5 both look vaguely like 16-sector tracks, but not quite. But when I inspect the half track between them, things get even weirder: --v-- COPY ][ PLUS BIT COPY PROGRAM 8.4 (C) 1982-9 CENTRAL POINT SOFTWARE, INC. --------------------------------------- TRACK: 04.50 START: 263E LENGTH: 1809 ^^^^^ 27B8: 96 96 96 96 96 96 96 96 VIEW 27C0: 96 96 96 96 96 96 96 96 27C8: 96 96 96 96 96 96 96 96 27D0: 96 96 96 96 96 96 96 96 27D8: 96 96 96 D5 AA AD AA AB <-27DB ^^^^^^^^ ^^^^^ prologue V=001 27E0: AE AB AF AB AB AB DE AA ^^^^^ ^^^^^ ^^^^^ ^^^^^ T=$09 S=$0B chksm epilogue 27E8: EA B5 FE FF FF FF FF D5 27F0: AA AD 96 96 96 96 96 96 FIND: 27F8: 96 96 96 96 96 96 96 96 D5 AA --^-- Track 4.5 is a lightly modified 16- sector track, except the address prologue is identical to the data prologue ("D5 AA AD"), and according to the address field, it's track 9. 4.5 x 2 = 9, but otherwise it seems arbitrary. Disk Fixer looks like a standard DOS 3.3 track $11 has a standard disk catalog T01,S09 -> startup program is "]A" followed by some control characters Why didn't COPYA work? tracks 4 and 5 are unreadable because the real data is in between Why didn't Locksmith FDB work? some sort of runtime check to ensure that track 4.5 has the required data and/or structure Why didn't EDD work? Real data being stored on track 4.5. Or possibly unused data, but it's being checked at runtime. Either way, I tested on real hardware to bit copy track 4.5 onto a floppy, and the copy booted right up. So that half track is definitely the key to this copy protection. Next steps: 1. find routine to read track 4.5 2. save the data somewhere else (if used, otherwise just disable it) 3. declare victory (*) (*) go to the gym ~ Chapter 1 You Had Me At "]A..." Booting the disk and attempting to do anything is... protected. [S6,D1=non-working copy] ]PR#6 BREAK ]LIST PROTECTED ]CATALOG PROTECTED ]FP ]CATALOG PROTECTED The first order of business is removing the control characters from the file name and attempting to LIST it from another disk that isn't so, um, PROTECTED. [Disk Fixer] [T11,S0F] --v-- -------------- DISK EDIT -------------- TRACK $11/SECTOR $0F/VOLUME $FE/BYTE$0E --------------------------------------- $00: 00 11 0E 00 00 00 00 00 @QN@@@@@ $08: 00 00 00 12 0F 02 DD C1 @@@ROB]A ^^^^^ $10: 8C 8B 82 A0 A0 A0 A0 A0 ... ^^^^^^^^ I'll change it to "HELLO" $18: A0 A0 A0 A0 A0 A0 A0 A0 $20: A0 A0 A0 A0 A0 A0 A0 A0 $28: A0 A0 A0 A0 02 00 12 0D B@RM $30: 02 DD C2 8C 8B 82 A0 A0 B]B... $38: A0 A0 A0 A0 A0 A0 A0 A0 $40: A0 A0 A0 A0 A0 A0 A0 A0 --^-- [S6,D1=non-working copy] [S5,D1=my work disk] ]PR#5 ... ]CATALOG,S6 C1983 DSR^C#254 063 FREE A 002 HELLO A >59 ]B A 051 ]C B 047 ]D B 010 ]E.BSHP B 034 ]F1 B 034 ]F2 B 034 ]F3 B 034 ]F4 B 034 ]F5 B 002 ]G.SLIB A 011 ]H A 006 ]I A 014 ]J A 005 ]K B 004 ]L *T 002 ]M *T 002 ]N T 002 ]O B 002 ]P A >60 DEMO ]LOAD HELLO ]LIST 0 REM COPYRIGHT 1986 ZYKRON CO .PR#6 10 C$ = CHR$ (4) 15 CALL 45995 20 POKE 50,128 21 PRINT C$;"BLOAD ]P" 22 POKE 2,9: POKE 47100,0: CALL 16384: POKE 47100,1 23 POKE 1012,0 24 IF PEEK (46989) < > 173 THEN CALL 49282 25 PRINT C$;"BRUN ]L" 30 PRINT C$;"RUN ]I" I'm pretty sure there was supposed to be an Applesoft trick on line 0 where it puts a Ctrl-D character at the start of a new line and DOS interprets it as a command. (YES CHILDREN, GATHER 'ROUND as I tell you the tale of the early computers, where listing a program could execute arbitrary DOS commands.) But they missed, so it doesn't reboot from the misaligned "PR#6" command and just lists it instead. Anyway. 45995 is $B3AB. What's at $B3AB? That's part of the disk volume name (usually "DISK VOLUME", but things like Diversi- DOS and Pronto-DOS had their own titles in there). At any rate, it's not executable code. Except on this disk, where it is. Returning to my trusty Disk Fixer sector editor, I can see what ends up at $B3AB by reading track 2, sector 2. --v-- -------------- DISK EDIT -------------- TRACK $02/SECTOR $02/VOLUME $FE/BYTE$AB --------------------------------------- $80: C5 B5 18 90 01 38 08 8D E5X.A8H. $88: C5 B5 A9 00 85 48 20 7E E5)@.H > $90: AE 28 AE 9B B3 9A 60 11 .(..3. Q $98: 0F 00 00 EE 00 01 00 00 O@@n@A@@ $A0: 00 00 FF FF 01 0A 64 A0 @@..AJ$ $A8: A0 A0 A0 60 D2 76 78 79 R689 ^^ $B0: 71 60 54 48 47 49 52 59 1 THGIRY $B8: 50 4F 43 04 11 0F 03 00 POCDQOC@ --^-- On this disk, $B3AB is an RTS. So the CALL on line 15 does nothing, except of course verify that you booted from the original disk's "PROTECTED" DOS. That's great. DRM is great. ~ Chapter 2 Double Your Pleasure, Double Your Fun Line 22 is more interesting. We've BLOADed a file (the name looks normal but it, too, is riddled with control character) and now we're calling 16384, which is $4000. If I delete line 15 and line 20, then put an "END" statement on line 22, I can let the program BLOAD the file for me and return control. ]15 ]20 ]22 END ]RUN ]CALL -151 *4000L ; get address of RWTS parameter table 4000- 20 E3 03 JSR $03E3 ; stash it in $00/$01 4003- 84 00 STY $00 4005- 85 01 STA $01 ; $02 was set to 9 in the BASIC program ; (line 22) 4007- A5 02 LDA $02 ; set that as the track 4009- A0 04 LDY #$04 400B- 91 00 STA ($00),Y ; RWTS command = 0 (seek) 400D- A9 00 LDA #$00 400F- A0 0C LDY #$0C 4011- 91 00 STA ($00),Y ; volume 0 (wildcard) 4013- A9 00 LDA #$00 4015- A0 03 LDY #$03 4017- 91 00 STA ($00),Y ; execute the seek 4019- 20 E3 03 JSR $03E3 401C- 20 D9 03 JSR $03D9 ; reset DOS 401F- A9 00 LDA #$00 4021- 85 48 STA $48 ; get the slot number 4023- A0 01 LDY #$01 4025- B1 00 LDA ($00),Y 4027- AA TAX ; turn on drive motor manually (after ; the RWTS call turned it off on the ; way out) 4028- BD 89 C0 LDA $C089,X 402B- BD 8E C0 LDA $C08E,X ; ($00) -> $1000 402E- A9 00 LDA #$00 4030- 85 00 STA $00 4032- A9 10 LDA #$10 4034- 85 01 STA $01 ; find an $FF nibble 4036- A0 00 LDY #$00 4038- BD 8C C0 LDA $C08C,X 403B- 10 FB BPL $4038 403D- C9 FF CMP #$FF 403F- D0 F7 BNE $4038 ; and another (or start over) 4041- BD 8C C0 LDA $C08C,X 4044- 10 FB BPL $4041 4046- C9 FF CMP #$FF 4048- D0 EE BNE $4038 ; find a non-$FF nibble 404A- BD 8C C0 LDA $C08C,X 404D- 10 FB BPL $404A 404F- C9 FF CMP #$FF 4051- F0 F7 BEQ $404A 4053- D0 05 BNE $405A ; store raw disk nibbles at $1000+ 4055- BD 8C C0 LDA $C08C,X 4058- 10 FB BPL $4055 405A- 91 00 STA ($00),Y 405C- E6 00 INC $00 405E- D0 F5 BNE $4055 4060- E6 01 INC $01 4062- A5 01 LDA $01 ; until $4000 4064- C9 40 CMP #$40 4066- 90 ED BCC $4055 ; turn off drive motor 4068- BD 88 C0 LDA $C088,X ; copy the third nibble 406B- AD 02 10 LDA $1002 406E- 8D 8D B7 STA $B78D 4071- 60 RTS And that's... it? I'm confused. What does track 9 have to do with anything? ~ Chapter 3 Don't Mess With The Zohan If I missed something, it was part of the BASIC program that got us here. Here, again, is line 22 that called the program we called at $4000: 22 POKE 2,9: POKE 47100,0: CALL 16384: POKE 47100,1 I know how address $02 is used -- it ends up as the track number in the seek we're doing (read at $4007). But there are two other POKEs here, surrounding the CALL like bookends: 47100. 47100is $B7FC. We set it to 0, then call this protection routine, then set it (back?) to 1. What's $B7FC? According to the indispensible "Beneath Apple DOS" (p. 8-35), it's part of the DCT. We're messing with the DCT. Nobody messes with the DCT. DCT ("Device Characteristics Table") starts at $B7FB, is only four bytes long, and should never, ever change: DCT+0 - device type (should be $00) DCT+1 - phases per track (should be $01) DCT+2 - motor on time count (should be $EF, $D8) But we are changing it, specifically the "phases per track" field at $B7FC, from #$01 to #$00. I don't know what effect this has, because literally no one ever does this. One phase is half a track. One track is two phases. These are just definitions. If you want to seek to track N, you tell the RWTS to seek to phase N*2. All disks work this way. Except this one. I pored through memory until I found where the DCT is used. Here, shortly after the RWTS entry point at $BD00, we get the DCT address from the RWTS parameter table: BD42- A0 06 LDY #$06 BD44- B1 48 LDA ($48),Y BD46- 99 36 00 STA $0036,Y BD49- C8 INY BD4A- C0 0A CPY #$0A BD4C- D0 F6 BNE $BD44 Among other things, that will set up ($3C) to point to the DCT, because it's taking RWTS+6 and putting it in $36+6. Then, when we try to switch to a different track, we see this code: *BE3BL ; accumulator has the desired track at ; this point ($00-$22 on a normal disk) BE3B- 48 PHA ; get DCT+1 (documented as "phases per ; track") BE3C- A0 01 LDY #$01 BE3E- B1 3C LDA ($3C),Y ; look at bit 0 BE40- 6A ROR BE41- 68 PLA ; if bit 0 is 0, skip ahead to seek BE42- 90 08 BCC $BE4C ; otherwise, multiply the desired track ; by 2 (now $00-$44) BE44- 0A ASL ; now seek BE45- 20 4C BE JSR $BE4C BE48- 4E 78 04 LSR $0478 BE4B- 60 RTS Then $BE4C initializes the drive motor and seeks to the phase it was given. So there are really only two code paths, one that moves two phases per track, and another that moves one phase per track. By changing DCT+1 from #$01 to #$00, we've told the drive seek routine to treat its input as a phase, not a track. We're passing the accumulator directly to the drive seek routine at $BE4C, without ever multiplying it by 2 (with the "ASL" at $BE44). DCT+1 isn't really "phases per track." It's a boolean, either 0 or 1. 1 - seek by track (default) 0 - seek by phase So we're seeking to track 4.5. Boom. ~ Chapter 4 In Which Our Adventure Comes To A Satisfying Conclusion As usual, bypassing the copy protection is orders of magnitude easier than understanding it. The assembly routine at $4000 reads disk nibbles from track 4.5 and copies the third one to $B78D. Then the BASIC program that called it reads that value and checks if it's 173 (#$AD). I can change the routine to unconditionally set that address to the correct value. [Disk Fixer] ["D"irectory mode] [Select "]P" file] ...takes me to T0A,S03 T0A,S03,$04 -> A9 AD 8D 8D B7 60 ...which looks like this at runtime: *4000L 4000- A9 AD LDA #$AD 4002- 8D 8D B7 STA $B78D 4005- 60 RTS Quod erat liberandum. ~ Changelog 2019-03-26 - fix minor corruption in two graphics files 2018-07-05 - initial release --------------------------------------- A 4am crack No. 1770 ------------------EOF------------------