--------------Microzine 19------------- A 4am crack 2015-08-03 -------------------. updated 2015-09-06 |___________________ Name: Microzine 19 Genre: educational Year: 1987 Publisher: Scholastic, Inc. Media: one double-sided 5.25-inch disk OS: DOS 3.3 Other cracks: Asimov has several uncredited cracks Similar cracks: Microzine 21 (crack no. 389) Microzine 23 (crack no. 152) ~ Chapter 0 In Which Various Automated Tools Fail In Interesting Ways COPYA no errors, but copy loads DOS, swings to a high-numbered track, and reboots Locksmith Fast Disk Backup ditto EDD 4 bit copy (no sync, no count) ditto In my experience, computers do not spontaneously reboot unless someone tells them to. Disk Fixer T00 -> looks like DOS 3.3 RWTS T00-T02 -> looks like full DOS 3.3 T01,S09 -> startup program is "W.SPC" Why didn't any of my copies work? probably a nibble check in the startup program A well-timed gets me a prompt, but any command reboots again. Next steps: 1. Load and trace the startup program 2. Find and disable the nibble check 3. There is no step 3 (I hope) ~ Chapter 1 In Which We Play Push-Me-Pull-You With The Stack, And Frankly The Stack Appreciates Playing Games Every Now And Again, I Mean Really, Life Doesn't Have To Be All Business All The Time [S6,D1=non-working Locksmith FDB copy] [S5,D1=my work disk] ]PR#5 ... ]BLOAD W.SPC,S6,D1 ]CALL -151 *BF55.BF56 ; location of last BLOAD ; in Diversi-DOS 64K BF55- 00 90 *9000L ; get address of RWTS parameter table 9000- 20 E3 03 JSR $03E3 9003- 85 FB STA $FB 9005- 84 FA STY $FA ; pushing bytes to the stack out of ; nowhere is a little suspicious 9007- A9 C5 LDA #$C5 9009- 48 PHA ; copy some local values into the RWTS ; parameter table 900A- A9 00 LDA #$00 900C- 85 FC STA $FC 900E- A2 03 LDX #$03 9010- BC 49 90 LDY $9049,X 9013- 91 FA STA ($FA),Y 9015- CA DEX 9016- 10 F8 BPL $9010 9018- 8A TXA ; more stack fiddling 9019- 48 PHA At this point, we've pushed two bytes on the stack -- $C5 and $FF. In other words, an RTS right now would pop those values off the stack, add 1, and "return" to... $C600. Which reboots. There is absolutely no reason for legitimate code to do this. There is, however, one very good reason for illegitimate code to do this: because it wants to reboot but doesn't want lazy hackers searching for the hex sequence "4C 00 C6" and finding this routine within seconds. 901A- 20 3C 90 JSR $903C *903CL ; call the RWTS (most likely just to ; move the drive head to the proper ; position for an impending nibble ; check) 903C- 20 E3 03 JSR $03E3 903F- 20 D9 03 JSR $03D9 9042- A9 00 LDA #$00 9044- 85 48 STA $48 ; if that fails, off to The Badlands 9046- B0 5D BCS $90A5 9048- 60 RTS Caller was $901A, so resuming at $901D: *901DL ; get the slot number (x16) from the ; RWTS parameter table and put it in X 901D- A0 01 LDY #$01 901F- B1 FA LDA ($FA),Y 9021- AA TAX ; don't know what this does yet 9022- 20 73 90 JSR $9073 *9073L ; turning on the drive motor manually ; is always suspicious 9073- BD 89 C0 LDA $C089,X ; set up a Death Counter 9076- A9 56 LDA #$56 9078- 85 FD STA $FD 907A- A9 08 LDA #$08 907C- C6 FC DEC $FC 907E- D0 04 BNE $9084 ; If the Death Counter hits zero, that ; would be bad. Which part of "Death ; Counter" sounded good to you, anyway? 9080- C6 FD DEC $FD 9082- F0 3C BEQ $90C0 ; look for a specific nibble ($FB) 9084- BC 8C C0 LDY $C08C,X 9087- 10 FB BPL $9084 9089- C0 FB CPY #$FB 908B- D0 ED BNE $907A 908D- F0 00 BEQ $908F ; 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) 908F- EA NOP 9090- EA NOP ; read data latch (note: no BPL loop ; here, we're just reading it once) 9091- 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) 9094- C0 08 CPY #$08 ; rotate the carry into the low bit of ; the accumulator 9096- 2A ROL ; if we just rolled a "1" bit out of ; the high bit of the accumulator, take ; this branch 9097- B0 0B BCS $90A4 ; next nibble needs to be $FF 9099- BC 8C C0 LDY $C08C,X 909C- 10 FB BPL $9099 ; ...otherwise we start over 909E- C0 FF CPY #$FF 90A0- D0 D8 BNE $907A ; loop back to get next nibble 90A2- F0 EB BEQ $908F ; execution continues here (from $9097) ; get another nibble 90A4- BC 8C C0 LDY $C08C,X 90A7- 10 FB BPL $90A4 ; stash it in zero page 90A9- 84 FC STY $FC ; if the accumulator is anything but ; %00001010, start over 90AB- C9 0A CMP #$0A 90AD- D0 CB BNE $907A 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 90AF- BD 8C C0 LDA $C08C,X 90B2- 10 FB BPL $90AF ; more bit twiddling 90B4- 38 SEC 90B5- 2A ROL ; AND it with the previously stashed ; nibble 90B6- 25 FC AND $FC 90B8- 49 FF EOR #$FF ; branch to failure path 90BA- D0 04 BNE $90C0 ; success path falls through to here -- ; turn off the drive motor and return ; to the caller 90BC- DD 88 C0 CMP $C088,X 90BF- 60 RTS ; failure path (a.k.a. "The Badlands", ; from which there is no return) 90C0- A8 TAY 90C1- DD 88 C0 CMP $C088,X ; manually pop the return address of ; the immediate caller (which leaves ; the manually pushed $C5FF address on ; the top of the stack to "return" to) 90C4- 68 PLA 90C5- 68 PLA ; destroy all trace of this program in ; memory 90C6- 99 00 90 STA $9000,Y 90C9- C8 INY 90CA- C0 8B CPY #$8B 90CC- D0 F8 BNE $90C6 ; "return" to $C5FF+1, i.e. reboot 90CE- 60 RTS That explains the behavior I saw on my non-working copy. ~ Chapter 2 In Which, Best Case, We Get Stuck In An Infinite Loop (But Somehow It Works Anyway) Meanwhile, the success path returns to the real caller and continues execution at $9025: ; pop the bogus return value ($C5FF) ; off the stack 9025- 68 PLA 9026- 68 PLA ; Route a series of bytes through the ; DOS output vector at ($36). This is ; how binary files "execute" DOS 3.3 ; commands, like PRINT CHR$(4)"..." in ; Applesoft BASIC. This is totally ; legitimate code, cleverly disguised ; as an infinite loop. 9027- A0 00 LDY #$00 9029- 84 FE STY $FE 902B- B9 4D 90 LDA $904D,Y 902E- 09 80 ORA #$80 9030- 20 39 90 JSR $9039 9033- A4 FE LDY $FE 9035- C8 INY 9036- 4C 29 90 JMP $9029 9039- 6C 36 00 JMP ($0036) Let's see what it's printing. *904D. 904D- 04 D2 D5 9050- CE A0 C8 C5 CC CC CF 20 9058- 20 20 20 20 20 20 20 20 9060- 20 20 20 20 20 20 20 20 9068- 20 20 20 20 20 20 20 20 9070- 20 0D 00 *FC58G N 400<904D.9072M DRUN HELLO M@ Aha! It's not an infinite loop after all. Well, it is, technically, but it won't actually run forever. It's not easy to show in plain text, but the initial "D" and the final "M@" are in inverse. So that string starts with Ctrl-D and ends with Ctrl-M and a null byte. Since DOS is already loaded, printing this through the DOS vector will execute that command, as if you typed it from a prompt yourself. Since that program never returns to the caller, it will break out of the seemingly "infinite" loop. Since there are no side effects to the copy protection routine, I can change this file to simply skip over it and go straight to running the real startup program (at $9027). Turning to my trusty Disk Fixer sector editor, I press "D" to enter directory mode, select "W.SPC", and I find this code on T0D,S04. The first four bytes are the address and length (as with any DOS 3.3 binary file), so the fifth byte is where I need to put an "RTS". T0D,S04,$04 change "20 E3 03" to "4C 27 90" [S6,D1=patched copy] ]PR#6 ...grinds and crashes at $9D86... Wait, what? ~ Chapter 3 In Which We Use Latin Phrases We Don't Fully Understand After minutes of furious investigation, I hit upon the source of the problem: the disk volume number. The original disk uses disk volume 001, but the copy I got out of Locksmith Fast Disk Backup just copies sector data from one disk to another. The blank disk I started with was initialized with disk volume 254 (this is encoded in every sector's address field), and Locksmith FDB never changed that. Why is this a problem? Well, besides appearing in every sector's address field, the volume number is stored in four different places when a disk is initialized with DOS 3.3: 1. $B7EB (T00,S01,$EB), in the RWTS parameter table used by boot1 to load DOS from tracks 0-2 ["Beneath Apple DOS", p. 8-35] 2. $B7F6 (T00,S01,$F6), also in the RWTS parameter table, as the "last found" disk volume 3. $AA66 (T01,S09,$66), in the parsed keyword table used by DOS to load the startup program (and every other file loaded after that) [ibid., p. 8-21] 4. $B3C1 (T11,S00,$06), in the VTOC header [ibid., p. 8-32] My (non-working) copy has a $01 in each of those locations. Since this doesn't match the actual disk volume number in the address fields, every sector read fails and DOS never loads. (Why did it work when I booted from my work disk? Because that loaded DOS from a separate disk that was already disk volume 254, thus matching up with the actual disk volume number in my non-working copy's address fields.) Using my trusty Disk Fixer sector editor, I changed each of the aforementioned locations to $FE. T00,S01,$EB change 01 to FE T00,S01,$F6 change 01 to FE T01,S09,$66 change 01 to FE T11,S06,$06 change 01 to FE Success! My copy finally boots and runs on its own. There doesn't appear to be any further copy protection. (Note to self: add this to a future version of Post-Demuffin Patcher.) 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-03 - initial release --------------------------------------- A 4am crack No. 390 ------------------EOF------------------