----------Greeting Card Maker---------- A 4am crack 2015-04-26 -------------------. updated 2015-09-06 |___________________ Name: Greeting Card Maker Genre: graphics Year: 1986 Credits: Produced by Michael Feinberg Art Director: Hilary Mills Art: Steve Snyder, Karen Elliott, Barbara McCosh, Louisa Leary, Sheila Lucas, Phil Cassel Verse: Paula Polley and Richard Pels Publisher: Activision, Inc. Media: single-sided 5.25-inch floppy OS: DOS 3.3 Other versions: none (preserved here for the first time) Similar cracks: Paper Models: The Christmas Kit (4am crack no. 227) ~ Chapter 0 In Which Various Automated Tools Fail In Interesting Ways COPYA no errors, but copy hangs after selecting "(1) Greeting Card Maker" from the main menu Locksmith Fast Disk Backup ditto EDD 4 bit copy (no sync, no count) ditto Copy ][+ nibble editor nothing suspicious Disk Fixer looks like unmodified DOS 3.3 T11 -> regular disk catalog T01,S09 -> startup program is "HELLO" Why didn't my copies work? Probably a nibble check called by the startup program. Activision loves "invisible" nibble checks that don't fail immediately but have a side effect. To verify my intuition that the DOS is unmodified (and the copy protection is self-contained somewhere else), I'll boot from a DOS 3.3 master disk and try to run the program. [S6,D1=original disk] [S5,D1=DOS 3.3 master disk] ]PR#5 ... ]RUN HELLO,S6 ...works... [S6,D1=non-working copy] [S5,D1=DOS 3.3 master disk] ]PR#5 ... ]RUN HELLO,S6 ...fails identically to booting the non-working copy directly... Next steps: 1. Trace the HELLO program 2. Find the nibble check and document any side effects 3. Disable the nibble check while maintaining the side effects ~ Chapter 1 In Which It All Starts So Innocently [S6,D1=original disk] ]PR#6 ]CATALOG DISK VOLUME 254 *A 009 HELLO *B 005 TB *B 002 SE *B 015 TR *B 013 LRG HAWTHRN *B 013 LRG FUTURA *B 013 SAPHIRE *B 013 OLD ENGLISH *B 013 MED TIMES *B 004 SMALL FONT *B 013 MED STENCIL *B 013 TC *B 005 IF *B 002 WC *B 004 TA *B 015 IP *B 028 PP *B 003 PF *B 003 TD *B 003 BL *B 002 CC B 002 PS *B 002 FONTS 1 /FNT *B 002 VALENTINE EMBRACE /VRS *A 039 ADBK *B 033 TITLE PAGE *B 002 FONTS 2 /FNT *B 002 CZ *B 003 ADDR.OBJ *B 016 CLOWN /PT2 *B 010 ROSE /PT0 *B 028 GA *B 017 GB *B 017 GC *B 010 GD *B 021 GE *B 002 PATTERNS 1 /PAT *B 003 PLACEMENT 1/GRP *B 004 FANCY /BRD *B 003 HEART /OBT *B 003 PLACEMENT 2/GRP *B 003 BIRTHDAY /DES *B 002 PATTERNS 2 /PAT *B 009 BABY DUCK /PT1 *B 012 DEER /PT3 *B 002 CHRISTMAS MERRY /VRS *B 003 CHRISTMAS 1/DES *B 003 FLOWER /OBT *B 026 RABBIT /SCN *B 002 HANAKKAH HAPPY /VRS *B 002 MOTHERS DAYCLEAR /VRS *B 002 FATHERS DAYCLEAR /VRS *B 002 HALLOWEEN SCARY /VRS *B 002 BIRTHDAY THINKING /VRS B 002 PROGRAM SETUP *B 002 WEDDING BEST /VRS *B 002 GRADUATION STEP ONE /VRS *B 002 NEW BABY LITTLE ONE /VRS *B 002 GET WELL TOP /VRS *B 002 SYMPATHY CONDOLENCES/VRS *B 002 GENERAL THINKING /VRS *B 002 NEW YEAR NEW HOPES /VRS ]LOAD HELLO ]LIST 10 ONERR GOTO 10 20 PRINT "NOMON C,I,O" 30 HOME 40 PRINT "MAXFILES 1" 50 POKE 49286,1 . . loads TITLE PAGE and displays it . 155 PRINT "BLOAD PROGRAM SETUP, A$93C4" 160 PRINT "BLOAD GE" 170 PRINT "BLOAD GC" . . [prints credits and waits] . 420 PRINT "BLOAD GB" 430 HOME 440 HTAB 10: VTAB 4 450 PRINT "Which would you like ?" 460 HTAB 1: VTAB 7 470 PRINT : PRINT "(1) Greeting Card Maker" 480 PRINT : PRINT "(2) Address Book/Envelope Maker" 490 PRINT : PRINT "(3) Setup Pr ogram Interface" 500 HTAB 31: VTAB 4 510 POKE - 16368,0 520 GET A$ 530 IF A$ = "1" THEN 1030 . . . 1030 PRINT 1040 PRINT "BRUN GA" My non-working copy got as far as displaying that main menu at line 430, but selecting "1" accessed the disk and froze. So I'll need to trace into the "GA" binary. ~ Chapter 2 In Which It All Goes Wrong So Quickly ]BLOAD GA ]CALL -151 *AA72.AA73 ; last BLOAD address AA72- 00 08 *800L 0800- 4C 00 20 JMP $2000 *2000L ; set reset vector to The Badlands ; (presumably) 2000- A9 0E LDA #$0E 2002- 8D F2 03 STA $03F2 2005- A9 22 LDA #$22 2007- 8D F3 03 STA $03F3 200A- 49 A5 EOR #$A5 200C- 8D F4 03 STA $03F4 ; counters? or an address? 200F- A9 11 LDA #$11 2011- 85 FA STA $FA 2013- A9 22 LDA #$22 2015- 85 FB STA $FB ; suspicious 2017- A9 C5 LDA #$C5 2019- 48 PHA ; memory move 201A- A9 00 LDA #$00 201C- 85 FC STA $FC 201E- A2 03 LDX #$03 2020- BC 3D 20 LDY $203D,X ; ah, it was an address 2023- 91 FA STA ($FA),Y 2025- CA DEX 2026- 10 F8 BPL $2020 ; suspicious 2028- 8A TXA 2029- 48 PHA We've now pushed $C5/$FF to the stack, so an RTS right now would jump to $C600 and reboot slot 6. Let's try to avoid that. 202A- 20 41 20 JSR $2041 *2041L ; call RWTS to position drive head 2041- A5 FB LDA $FB 2043- A4 FA LDY $FA 2045- 20 B5 B7 JSR $B7B5 2048- A9 00 LDA #$00 204A- 85 48 STA $48 204C- 90 02 BCC $2050 ; on error, pop the real return address ; and leave $C5/$FF at the top of the ; stack, then RTS to reboot 204E- 68 PLA 204F- 68 PLA 2050- 60 RTS My non-working copy didn't reboot, so I guess it got past this. Continuing at $202D... *202DL ; get slot number (x16) into X 202D- A0 01 LDY #$01 202F- 8C 1D 22 STY $221D 2032- B1 FA LDA ($FA),Y 2034- AA TAX ; here we go 2035- 20 51 20 JSR $2051 We're closing in on the copy protection routine. Can you feel it? I swear I can feel it. The random PHA instructions are a good clue. Disk seeks for no apparent reason. The no-second-chances approach to error handling. This is unfriendly territory. ~ Chapter 3 In Which We Forge Into Unfriendly Territory *2051L ; turn on drive motor manually ; (literally never not suspicious) 2051- BD 89 C0 LDA $C089,X ; initialize death counters 2054- A9 56 LDA #$56 2056- 85 FD STA $FD 2058- A9 08 LDA #$08 205A- C6 FC DEC $FC 205C- D0 04 BNE $2062 205E- C6 FD DEC $FD ; if death counter hits 0, branch 2060- F0 34 BEQ $2096 ; look for a specific nibble ($FB) 2062- BC 8C C0 LDY $C08C,X 2065- 10 FB BPL $2062 2067- C0 FB CPY #$FB 2069- D0 ED BNE $2058 206B- F0 00 BEQ $206D ; kill a few cycles (not pointless, ; because the disk spins independently ; of the CPU, so all of these low-level ; disk reads are highly time-sensitive) 206D- EA NOP 206E- EA NOP ; read data latch (note: no BPL loop ; here, we're just reading it once) 206F- BC 8C C0 LDY $C08C,X ; do a compare to set or clear the ; carry bit (among other things, but ; it's the carry bit we care about) 2072- C0 08 CPY #$08 ; rotate the carry into the low bit of ; the accumulator 2074- 2A ROL ; if we just rolled a "1" bit out of ; the high bit of the accumulator, take ; this branch 2075- B0 0B BCS $2082 ; next nibble needs to be $FF 2077- BC 8C C0 LDY $C08C,X 207A- 10 FB BPL $2077 ; ...otherwise we start over 207C- C0 FF CPY #$FF 207E- D0 D8 BNE $2058 ; loop back to get next nibble 2080- F0 EB BEQ $206D ; execution continues here (from $2075) ; get another nibble 2082- BC 8C C0 LDY $C08C,X 2085- 10 FB BPL $2082 ; stash it in zero page 2087- 84 FC STY $FC ; if the accumulator is anything but ; %00001010, start over 2089- C9 0A CMP #$0A 208B- D0 CB BNE $2058 I got lost several times trying to follow this routine. I think the easiest way to explain it is to show the difference between the original disk and my non-working copy. Here is the original disk, as seen by the Copy II+ nibble editor. Nibbles with extra "0" bits (timing bits) after them are displayed in inverse on an original machine, marked here with a "+" after the nibble. --v-- COPY ][ PLUS BIT COPY PROGRAM 8.4 (C) 1982-9 CENTRAL POINT SOFTWARE, INC. --------------------------------------- TRACK: START: 1B1E LENGTH: 17C1 1C70: 9F EB E5 FC D7 D7 D7 EE VIEW 1C78: FA E6 E6 FF FE F2 ED FD 1C80: FF EF ED BA BB DD AF E6 1C88: B7 A7 CB B7 DE AA EB FF 1C90: FF FF FF FB+FF FF+FF FF+ 1C98: FD FF+FF+FF+FF+FF+FF+FF+ 1CA0: FF+FF+D5 AA 96 AA AB AA 1CA8: AA AA AB AA AA DE AA EB+ 1CB0: FF+FF+FF+FF+FF+FF D5 AA --------------------------------------- A TO ANALYZE DATA ESC TO QUIT ? FOR HELP SCREEN / CHANGE PARMS Q FOR NEXT TRACK SPACE TO RE-READ --^-- It's easy to understand why a simple sector copy failed. The sequence that this code is looking for starts at offset $1C93, which is between the end of one sector and the beginning of the next. (The data epilogue is at $1C8C; the next address prologue is at $1CA2.) Sector copiers discard everything between those delimiters and rebuild the track with a default pattern of sync bytes. That pattern doesn't include an $FB nibble, so the nibble check fails. But the EDD bit copy also failed. Here is the original disk's pattern at offset $1C93: - $FB + timing bit - $FF - $FF + timing bit - $FF - $FF + timing bit And here is what the same part of the track looks like on my failed EDD copy: --v-- COPY ][ PLUS BIT COPY PROGRAM 8.4 (C) 1982-9 CENTRAL POINT SOFTWARE, INC. --------------------------------------- TRACK: START: 1B1E LENGTH: 17C1 1C70: 9F EB E5 FC D7 D7 D7 EE VIEW 1C78: FA E6 E6 FF FE F2 ED FD 1C80: FF EF ED BA BB DD AF E6 1C88: B7 A7 CB B7 DE AA EB FF 1C90: FF FF FF FB+FF FF FF+FF+ 1C98: FD FF+FF+FF+FF+FF+FF+FF+ 1CA0: FF+FF+D5 AA 96 AA AB AA 1CA8: AA AA AB AA AA DE AA EB+ 1CB0: FF+FF+FF+FF+FF+FF D5 AA --------------------------------------- A TO ANALYZE DATA ESC TO QUIT ? FOR HELP SCREEN / CHANGE PARMS Q FOR NEXT TRACK SPACE TO RE-READ --^-- A subtle difference! The sequence at offset $1C93 now looks like this: - $FB + timing bit - $FF - $FF - $FF + timing bit - $FF + timing bit This code is looking for $FF bytes with an alternating pattern of timing bit, no timing bit, timing bit, no timing bit. It doesn't find that on the bit copy, so it knows it's not running on an original disk. Continuing the code listing... ; get a nibble 208D- BD 8C C0 LDA $C08C,X 2090- 10 FB BPL $208D ; more bit twiddling 2092- 38 SEC 2093- 2A ROL ; AND it with the previously stashed ; nibble 2094- 25 FC AND $FC ; Success path falls through to here, ; but the failure path is also here ; (from $2060, when the death counters ; hit zero). In other words, we will ; always end up here regardless of ; whether the nibble check "passed," ; but the value in the accumulator will ; be wrong if the nibble check failed. 2096- 49 AA EOR #$AA ; The difference between the original ; disk and my non-working copy is right ; here, in the value stored in $20C0. ; It's not used right away, but I'll ; bet you anything that it will be used ; soon. 2098- 8D C0 20 STA $20C0 ; "This nibble check will self-destruct ; in ten seconds..." 209B- A9 00 LDA #$00 209D- A8 TAY 209E- 99 41 20 STA $2041,Y 20A1- C8 INY 20A2- C0 5D CPY #$5D 20A4- D0 F8 BNE $209E 20A6- 60 RTS And that's it. Whether the nibble check succeeds or fails, this routine returns to the caller. It doesn't even set a flag. The only difference is the value of $20C0. Continuing from $2038 (the first instruction after the JSR to $2051)... *2038L ; pop $C5/$FF off the stack 2038- 68 PLA 2039- 68 PLA ; continue elsewhere 203A- 4C A7 20 JMP $20A7 *20A7L ; a memory move 20A7- A9 0F LDA #$0F 20A9- 85 FA STA $FA 20AB- A9 20 LDA #$20 20AD- 85 FB STA $FB 20AF- A9 A7 LDA #$A7 20B1- 85 FC STA $FC 20B3- A9 20 LDA #$20 20B5- 85 FD STA $FD 20B7- 20 F6 21 JSR $21F6 ; another memory move 20BA- 20 25 22 JSR $2225 ; continue elsewhere 20BD- 4C CF 20 JMP $20CF *20CFL 20CF- AD CC 20 LDA $20CC 20D2- 18 CLC 20D3- 6D C0 20 ADC $20C0 20D6- 85 02 STA $02 20D8- AD CD 20 LDA $20CD 20DB- 38 SEC 20DC- ED C0 20 SBC $20C0 20DF- 85 03 STA $03 20E1- AD CE 20 LDA $20CE 20E4- 4D C0 20 EOR $20C0 20E7- 85 04 STA $04 There you go: three operations that rely on the correct value in $20C0. Luckily, this code is not obfuscated or even difficult to patch. I can do it right now without rebooting. *203A:4C 59 FF ; instead of JMP $20A7 *2000G *20C0 20C0- 55 The correct value is $55. Now I can hard-code that value on disk. [Disk Fixer] --> "D" for directory mode --> select file "GA" --> arrows to follow the binary (it starts at $0800, so press right arrow 24 times to get to the sector that loads at $2000) T20,S09,$C4 change "00" to "55" And neutralize the nibble check subroutine at $2051 by changing the first instruction to an "RTS": T20,S09,$55 change "BD" to "60" Quod erat liberandum. ~ Changelog 2015-09-06 - Vastly improved explanation of the actual protection routine. Thanks to qkumba for pointing out that my original explanation was inaccurate. 2015-04-26 - initial release --------------------------------------- A 4am crack No. 295 ------------------EOF------------------