-------Murder on the Mississippi------- A 4am crack 2015-08-26 -------------------. updated 2015-09-06 |___________________ Name: Murder on the Mississippi Genre: adventure Year: 1986 Credits: Created by Adam Bellin Produced by Brad Fregger Story written by Rob Swigart Graphics designed by Hilary Mills Apple adaptation by David Lubar Publisher: Activision, Inc. Media: double-sided 5.25-inch floppy OS: DOS 3.3 Previous cracks: The Dragon Lord / Digital Gang ~ Chapter 0 In Which Various Automated Tools Fail In Interesting Ways COPYA no errors, but copy hangs on boot (after showing DOS prompt) Locksmith Fast Disk Backup ditto EDD 4 bit copy (no sync, no count) ditto Copy ][+ nibble editor nothing suspicious Disk Fixer T00-T02 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 startup 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 ... [ immediately after prompt] BREAK ]CATALOG DISK VOLUME 254 A 002 HELLO T 003 DL B 034 PI.TITLE1 B 034 PI.TITLE2 B 034 CODE1 B 010 TXTSET B 009 NAMETXT B 016 INTEXT B 005 DOOR B 005 ODOOR B 010 MONDAV B 010 MONMSK B 004 NUMS B 007 BOOT B 034 CODE2 B 006 SECRD B 002 TABLO ]LIST 10 PRINT "EXECDL" Hmm. Turning to Copy ][+ (8.4), I can view the contents of the "DL" text file: --v-- VIEW FILES TEXT SLOT 6 DRIVE 1 DISK VOLUME 254 FILE: DL [RETURN]-CONTINUE, [ESC]-EXIT 218 --------------------------------------- CALL -151 2000:00 2001<2000.5FFEM C057:00 C050:00 C052:00 C054:00 BLOAD PI.TITLE1,A$4000 C055:00 C081 C081 BLOAD INTEXT,A$D000 BLOAD NAMETXT,A$800 BLOAD TXTSET BLOAD NUMS,A$1C00 BLOAD MONDAV,A$E000 BLOAD MONMSK,A$F000 BLOAD DOOR,A$EC40 BLOAD ODOOR,A$FC40 BLOAD SECRD,A$6C00 BLOAD TABLO,A$310 BLOAD CODE1,A$7000 BLOAD PI.TITLE2,A$2000 C054:FF 4000<2000.3FFFM C055:FF BLOAD CODE2,A$2000 BLOAD BOOT 1000G --^-- It looks like "BOOT" is the program at $1000. ]PR#6 ... ]BLOAD BOOT ]CALL -151 *AA72.AA73 AA72- 00 10 *1000L 1000- 4C 00 12 JMP $1200 *1200L ; set reset vector 1200- A9 09 LDA #$09 1202- 8D F2 03 STA $03F2 1205- A9 14 LDA #$14 1207- 8D F3 03 STA $03F3 120A- 49 A5 EOR #$A5 120C- 8D F4 03 STA $03F4 ; an address? ($FA) -> $140C 120F- A9 0C LDA #$0C 1211- 85 FA STA $FA 1213- A9 14 LDA #$14 1215- 85 FB STA $FB ; suspicious 1217- A9 C5 LDA #$C5 1219- 48 PHA ; selective memory moves 121A- A9 00 LDA #$00 121C- 85 FC STA $FC 121E- A2 03 LDX #$03 1220- BC 3D 12 LDY $123D,X ; ah, it was an address 1223- 91 FA STA ($FA),Y 1225- CA DEX 1226- 10 F8 BPL $1220 ; suspicious 1228- 8A TXA 1229- 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. 122A- 20 41 12 JSR $1241 *1241L ; ah, the memory moves we were just ; doing were setting values in an RWTS ; parameter table, which we are now ; using to position the drive head ; (not shown: it was a "seek" command) 1241- A5 FB LDA $FB 1243- A4 FA LDY $FA 1245- 20 B5 B7 JSR $B7B5 1248- A9 00 LDA #$00 124A- 85 48 STA $48 124C- 90 02 BCC $1250 ; if the RWTS command failed, pop the ; real return address and leave $C5/$FF ; at the top of the stack, then RTS to ; reboot 124E- 68 PLA 124F- 68 PLA 1250- 60 RTS My non-working copy didn't reboot, so I guess it got past this. Continuing from $122D... *122DL ; get slot number (x16) into X 122D- A0 01 LDY #$01 122F- 8C 18 14 STY $1418 1232- B1 FA LDA ($FA),Y 1234- AA TAX ; here we go 1235- 20 51 12 JSR $1251 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 2 In Which We Forge Into Unfriendly Territory *1251L ; turn on drive motor manually ; (literally never not suspicious) 1251- BD 89 C0 LDA $C089,X ; initialize Death Counter 1254- A9 56 LDA #$56 1256- 85 FD STA $FD 1258- A9 08 LDA #$08 125A- C6 FC DEC $FC 125C- D0 04 BNE $1262 125E- C6 FD DEC $FD ; if Death Counter hits 0, jump forward ; (more on this in a second) 1260- F0 34 BEQ $1296 ; look for a specific nibble ($FB) 1262- BC 8C C0 LDY $C08C,X 1265- 10 FB BPL $1262 1267- C0 FB CPY #$FB 1269- D0 ED BNE $1258 126B- F0 00 BEQ $126D ; 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) 126D- EA NOP 126E- EA NOP ; read data latch (note: no BPL loop ; here, we're just reading it once) 126F- 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) 1272- C0 08 CPY #$08 ; rotate the carry into the low bit of ; the accumulator 1274- 2A ROL ; if we just rolled a "1" bit out of ; the high bit of the accumulator, take ; this branch 1275- B0 0B BCS $1282 ; next nibble needs to be $FF 1277- BC 8C C0 LDY $C08C,X 127A- 10 FB BPL $1277 ; ...otherwise we start over 127C- C0 FF CPY #$FF 127E- D0 D8 BNE $1258 ; loop back to get next nibble 1280- F0 EB BEQ $126D ; execution continues here (from $1275) ; get another nibble 1282- BC 8C C0 LDY $C08C,X 1285- 10 FB BPL $1282 ; stash it in zero page 1287- 84 FC STY $FC ; if the accumulator is anything but ; %00001010, start over 1289- C9 0A CMP #$0A 128B- D0 CB BNE $1258 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 128D- BD 8C C0 LDA $C08C,X 1290- 10 FB BPL $128D ; more bit twiddling 1292- 38 SEC 1293- 2A ROL ; AND it with the previously stashed ; nibble 1294- 25 FC AND $FC ; Success path falls through to here, ; but the failure path is also here ; (from $1260, when the Death Counter ; hits 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. 1296- 49 AA EOR #$AA ; The difference between the original ; disk and my non-working copy is right ; here, in the value stored in $12C0. ; It's not used right away, but I'll ; bet it will be used soon. 1298- 8D C0 12 STA $12C0 ; "This nibble check will self-destruct ; in ten seconds..." 129B- A9 00 LDA #$00 129D- A8 TAY 129E- 99 41 12 STA $1241,Y 12A1- C8 INY 12A2- C0 5D CPY #$5D 12A4- D0 F8 BNE $129E 12A6- 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 $12C0. ~ Chapter 3 In Which We See How It All Fits Together Continuing from $1238 (the next instruction after calling the nibble check)... *1238L ; pop $C5/$FF off the stack 1238- 68 PLA 1239- 68 PLA ; continue elsewhere 123A- 4C A7 12 JMP $12A7 *12A7L ; a memory move 12A7- A9 0F LDA #$0F 12A9- 85 FA STA $FA 12AB- A9 12 LDA #$12 12AD- 85 FB STA $FB 12AF- A9 A7 LDA #$A7 12B1- 85 FC STA $FC 12B3- A9 12 LDA #$12 12B5- 85 FD STA $FD 12B7- 20 F1 13 JSR $13F1 ; another memory move 12BA- 20 1D 14 JSR $141D ; continue elsewhere 12BD- 4C CF 12 JMP $12CF *12CFL 12CF- AD CC 12 LDA $12CC 12D2- 18 CLC 12D3- 6D C0 12 ADC $12C0 12D6- 85 02 STA $02 12D8- AD CD 12 LDA $12CD 12DB- 38 SEC 12DC- ED C0 12 SBC $12C0 12DF- 85 03 STA $03 12E1- AD CE 12 LDA $12CE 12E4- 4D C0 12 EOR $12C0 12E7- 85 04 STA $04 There you go: three operations that rely on the correct value in $12C0. Luckily, this code is not obfuscated or even difficult to patch. I can do it right now without rebooting. ; break to monitor after we've stored ; the magic value in $22C0 *129B:4C 59 FF *1000G *12C0 12C0- 55 The magic value is $55. Now I can write that value to disk. [Disk Fixer] --> "D" for directory mode --> select file "BOOT" --> arrows to follow the binary T10,S09,$C4 change "00" to "55" $1235 calls the nibble check; I can change that "JSR" to a "BIT" to bypass it altogether: T10,S09,$39 change "20" to "2C" 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-08-26 - initial release --------------------------------------- A 4am crack No. 426 ------------------EOF------------------