-----------Troll Sports Math----------- A 4am and san inc crack 2019-05-27 --------------------------------------- Name: Troll Sports Math: Math Word Problems for Grades 4-6 Genre: educational Year: 1991 Publisher: Troll Associates Platform: Apple ][+ or later Media: 3.5-inch disk Sides: 1 OS: UniDOS Previous cracks: none ~ Chapter 0 In Which Various Automated Tools Fail In Interesting Ways Copy ][+ 9.1 ("COPY" > "DISK") no read errors, but copy loads DOS then fills the screen with garbage (well, a repeating pattern, so more like structured garbage) and reboots CFFA 3000 import no read errors, but booting the disk image in an emulator exhibits the same behavior as the backup I made on real hardware with Copy ][+ I do not know if the original disk boots. The slim amount of documentation I received with the disk states that it requires a UniDisk 3.5 drive, which I do not have. However, legitimate disk read failures do not tend to fill the screen with structured garbage, so I'm guessing this is the failure mode of a run-time protection check. Next steps: 1. Trace the startup program 2. Find and disable the protection check 3. Declare victory(*) (*) go to the gym ~ Chapter 1 In Which Things Quickly Get Hairy Unlike most 3.5-inch disks, this does not boot ProDOS. Quick investigation reveals it runs some sort of DOS-on- 800K-disk thing called "UniDOS." I found a "UniDOS master" online that boots to a working DOS prompt. [S5,D1=UniDOS master] [S5,D2=non-working copy] ]PR#5 ]CATALOG,S5,D2 DISK VOLUME 001 A 056 HELLO B 006 T0 B 022 M B 014 M0 B 026 A B 010 A0 B 024 U B 010 A22 B 008 A21 B 010 A12 B 008 A11 B 010 A32 B 008 A31 B 010 B0 B 026 B B 010 B12 B 008 B21 B 010 B22 B 008 B31 B 010 B32 B 008 B11 B 010 C0 B 008 C11 B 026 C B 008 C21 B 008 C31 B 010 C22 B 010 C12 B 010 C32 B 014 M2 1109 FREE SECTORS That's... a lot of free sectors. I'm going to assume that the startup program is HELLO, like a 5.25-inch disk running DOS 3.3. ]LOAD HELLO ]LIST 10 CALL 14386 Oh great, the "BASIC" program is really just a shell for an assembly language program contained within it. ]CALL -151 *3832L ; set up, then call, the memory move ; routine in ROM ; copying $3857 -> $3FEA 3832- A9 57 LDA #$57 3834- 85 3C STA $3C 3836- A9 38 LDA #$38 3838- 85 3D STA $3D 383A- A9 6D LDA #$6D 383C- 85 3E STA $3E 383E- A9 3D LDA #$3D 3840- 85 3F STA $3F 3842- A9 EA LDA #$EA 3844- 85 42 STA $42 3846- A9 3F LDA #$3F 3848- 85 43 STA $43 384A- A0 00 LDY #$00 384C- 20 2C FE JSR $FE2C ; call the code we just moved 384F- 20 EA 3F JSR $3FEA Let's temporarily put an "RTS" there and let it call the memory move. *384F:60 *3832G *3FEAL ; check various machine identification ; bytes in ROM 3FEA- AD B3 FB LDA $FBB3 3FED- C9 06 CMP #$06 3FEF- D0 0F BNE $4000 3FF1- AD C0 FB LDA $FBC0 3FF4- C9 E0 CMP #$E0 3FF6- D0 08 BNE $4000 3FF8- AD DD FB LDA $FBDD 3FFB- C9 02 CMP #$02 3FFD- D0 01 BNE $4000 3FFF- 60 RTS 4000- 6C 2B 44 JMP ($442B) I'm not sure that these tests ever do anything but branch to $4000. On my enhanced Apple //e, $FBDD is #$A9, so we end up at $4000. *442B.442C 442B- CA 43 *43CAL ; save flags 43CA- 08 PHP ; check for Apple IIgs 43CB- D8 CLD 43CC- 38 SEC 43CD- 20 1F FE JSR $FE1F ; if carry is set, it's not a IIgs 43D0- B0 02 BCS $43D4 43D2- E2 ??? ; IIgs path 43D3- 30 A5 BMI $437A The underlying code is similar on 8-bit and 16-bit machines. I'll only step through the 8-bit path, which starts at $43D4. *43D4L ; save some zero page values 43D4- A5 06 LDA $06 43D6- 8D C7 43 STA $43C7 43D9- A5 07 LDA $07 43DB- 8D C8 43 STA $43C8 43DE- A0 00 LDY #$00 43E0- 84 06 STY $06 ; always branches 43E2- F0 01 BEQ $43E5 [43E4- AF ???] 43E5- A0 03 LDY #$03 43E7- A9 40 LDA #$40 ; always branches 43E9- D0 01 BNE $43EC 43EB- 5C ??? 43EC- 85 07 STA $07 ; always branches 43EE- D0 01 BNE $43F1 [43F0- 22 ???] 43F1- 20 24 44 JSR $4424 *4424L ; get a byte and "decrypt" it with EOR 4424- B1 06 LDA ($06),Y 4426- 49 E1 EOR #$E1 4428- 38 SEC 4429- 60 RTS ; always branches, because the routine ; at $4424 explicitly sets the carry 43F4- B0 01 BCS $43F7 [43F6- 43 ???] ; store the decrypted byte 43F7- 91 06 STA ($06),Y 43F9- C8 INY 43FA- D0 F5 BNE $43F1 All of that for a lightly obfuscated decryption loop of a page of memory at $4000. ; always branches 43FC- F0 01 BEQ $43FF [43FE- C7 ???] ; oh look, we're decrypting more pages 43FF- E6 07 INC $07 ; always branches 4401- D0 01 BNE $4404 [4403- 22 ???] 4404- A5 07 LDA $07 ; up to $4300 4406- C9 43 CMP #$43 ; loop back 4408- D0 E7 BNE $43F1 ; always branches, because if it was ; not equal we already branched back 440A- F0 01 BEQ $440D ;440C- 13 ??? ; another decryption loop, for part of ; the page at $4300 440D- 20 24 44 JSR $4424 4410- B0 01 BCS $4413 4412- 27 ??? 4413- 91 06 STA ($06),Y 4415- C8 INY ; up to $43C6 4416- C0 C7 CPY #$C7 ; loop back 4418- D0 F3 BNE $440D ; always branches 441A- F0 01 BEQ $441D [441C- 3C ???] 441D- 6C 21 44 JMP ($4421) *4421.4422 4421- 03 40 That's in the code we just decrypted, so let's do that. ; RTS instead of JMP *441D:60 ; execute the decryption loops without ; the initial PHP *43CBG Piece of cake. ~ Chapter 2 In Which It Is Most Definitely Not A Piece Of Cake And The Author Would Appreciate It If He Would Stop Calling It That Let's see what wonderous code awaits us after all that obfuscation, decryption, and indirection. *4003L ; munge reset vector, so you know stuff ; is getting serious 4003- A0 00 LDY #$00 4005- 8C F4 03 STY $03F4 ; I checked and this is the same as it ; is under DOS 3.3 -- the boot slot x16 4008- AD E9 B7 LDA $B7E9 400B- 4A LSR 400C- 4A LSR 400D- 4A LSR 400E- 4A LSR 400F- 09 C0 ORA #$C0 4011- 8D 19 40 STA $4019 4014- 8D 61 40 STA $4061 ; self-modified above by the boot slot 4017- AD FF C5 LDA $C5FF 401A- 18 CLC 401B- 69 03 ADC #$03 401D- 8D 60 40 STA $4060 ; drive (1 or 2) 4020- AD EA B7 LDA $B7EA 4023- C9 01 CMP #$01 4025- F0 17 BEQ $403E ; for reasons passing all understanding ; this protection check supports ; booting from drive 2 4027- A9 02 LDA #$02 4029- 8D 0E 41 STA $410E 402C- 8D 15 41 STA $4115 402F- 8D 1C 41 STA $411C 4032- 8D 27 41 STA $4127 4035- 8D 2F 41 STA $412F 4038- 8D 34 41 STA $4134 403B- 8D 40 41 STA $4140 ; check for IIgs again 403E- 38 SEC 403F- 20 1F FE JSR $FE1F 4042- 90 1E BCC $4062 ; fall through for 8-bit machine, and ; once again check a bunch of different ; machine identification bytes 4044- AD B3 FB LDA $FBB3 4047- C9 06 CMP #$06 4049- D0 11 BNE $405C 404B- AD C0 FB LDA $FBC0 404E- C9 00 CMP #$00 4050- D0 0A BNE $405C 4052- AD BF FB LDA $FBBF 4055- C9 05 CMP #$05 4057- D0 03 BNE $405C ; if hardware is unsupported, skip the ; protection check (I'm not sure if any ; Apple II model ever gets here) 4059- 4C E8 40 JMP $40E8 ; hardware checks out, let's do this 405C- 4C 7A 40 JMP $407A *407AL ; call a routine that takes parameters ; on the stack, branch to $409B if it ; fails (more on this in a moment) 407A- 20 5F 40 JSR $405F 407D- [04] 407E- [26 41] 4080- B0 19 BCS $409B ; again, but different parameters 4082- 20 5F 40 JSR $405F 4085- [04] 4086- [2E 41] 4088- B0 11 BCS $409B ; again 408A- 20 5F 40 JSR $405F 408D- [04] 408E- [33 41] 4090- B0 09 BCS $409B ; again 4092- 20 5F 40 JSR $405F 4095- [01] 4096- [0D 41] ; all done 4098- 4C E8 40 JMP $40E8 *40E8L ; copy this code to lower memory 40E8- A0 17 LDY #$17 40EA- B9 F6 40 LDA $40F6,Y 40ED- 99 00 02 STA $0200,Y 40F0- 88 DEY 40F1- 10 F7 BPL $40EA ; and execute it from there 40F3- 4C 00 02 JMP $0200 ; wipe the decrypted protection code 40F6- A9 00 LDA #$00 40F8- A8 TAY 40F9- 99 03 40 STA $4003,Y 40FC- 99 03 41 STA $4103,Y 40FF- 99 03 42 STA $4203,Y 4102- 99 03 43 STA $4303,Y 4105- 99 03 44 STA $4403,Y 4108- 88 DEY 4109- D0 EE BNE $40F9 ; restore flags (pushed at $43CA) 410B- 28 PLP ; return to caller with no further ; side effects 410C- 60 RTS That's the success path. But if any of the calls to $405F fail, we end up at $409B, which seems bad: ; The Badlands 409B- A9 00 LDA #$00 409D- 8D C9 40 STA $40C9 40A0- A9 C5 LDA #$C5 40A2- 8D CA 40 STA $40CA ; copy to lower memory 40A5- A0 18 LDY #$18 40A7- B9 B3 40 LDA $40B3,Y 40AA- 99 00 02 STA $0200,Y 40AD- 88 DEY 40AE- 10 F7 BPL $40A7 ; and continue from there 40B0- 4C 00 02 JMP $0200 ; wipe main memory 40B3- A0 00 LDY #$00 40B5- A9 03 LDA #$03 40B7- 84 06 STY $06 40B9- 85 07 STA $07 40BB- 91 06 STA ($06),Y 40BD- C8 INY 40BE- D0 FB BNE $40BB 40C0- E6 07 INC $07 40C2- A5 07 LDA $07 40C4- C9 C0 CMP #$C0 40C6- D0 F3 BNE $40BB ; reboot (this was self-modified at ; $409B to always jump to $C500 -- ; ironically this is NOT based on the ; boot slot, because I guess filthy ; pirates don't deserve that kind of ; consideration) 40C8- 4C C8 40 JMP $40C8 So we're doing a thing at $405F, four times but with different parameters, and if they all work, we clean up and return to the caller as if nothing happened. Let's see what we're doing at $405F. *405FL 405F- 4C 0A C5 JMP $C50A Oh, great. We're calling the SmartPort firmware. ~ Chapter 3 In Which Everyone Is Smart In Their Own Way SmartPort firmware is documented in "Apple IIgs Firmware Reference," ch. 7. --v-- This is an example of a standard SmartPort call: ; Call SmartPort command dispatcher SP_CALL JSR DISPATCH ; This specifies the command type DFB CMDNUM ; word pointer to the parameter list DW CMDLIST ; carry is set on an error BCS ERROR --^-- That's exactly what we're doing at $407A -- calling the command dispatch (adjusted for the boot slot). The next three bytes are a command number and the address of a parameter block. Then we branch to The Badlands at $409B on error. 407A- 20 5F 40 JSR $405F 407D- [04] 407E- [26 41] 4080- B0 19 BCS $409B In this first call, we're issuing the SmartPort command #$04 with a parameter block at $4126. This is a "control call" -- an extension mechanism to send commands that different devices can interpret in different ways. The control calls for the UniDisk 3.5 are documented later in chapter 7. (This, by the way, explains why the program "requires" a UniDisk 3.5 drive. It's not the program that requires it, it's the copy protection.) The parameter block at $4126 gives the details on this "control call." *4126. 4126- .. .. .. .. .. .. 03 01 4128- 2B 41 06 02 00 05 03 01 Taking this one byte at a time: $4126: $03 parameter count $4127: $01 unit number (possibly self- modified above to support running from drive 2) $4128: $412B address of "control list," i.e. the parameter block for this custom call $412A: $06 control code: "SetAddress" $412B: $0305 address within the UniDisk drive The firmware reference manual describes the "SetAddress" control call: --v-- This call is used to set the address in the UniDisk 3.5 controller's memory space that the Download call will load a 65C02 routine into. Care must be taken that the download address is set only to free space in the UniDisk 3.5 memory map. --^-- So we're setting up an environment to transfer executable code to the drive itself. Because that's not completely insane. (Yes, I'm aware that this was common on other platforms like the Commodore 64. That doesn't make it any less insane.) So what are we downloading? That's the next call, at $4082: 4082- 20 5F 40 JSR $405F 4085- [04] 4086- [2E 41] 4088- B0 11 BCS $409B Same deal, we're calling the SmartPort firmware with a control call. $4085 is #$04, a control call command. $4086 is $412E, the address of the parameter block. And we branch to The Badlands on error. Looking at the parameter block: *412E. 412E- .. .. .. .. .. .. 03 01 4130- 00 42 07 We see a similar structure as the first call, but with a different control code (at $4132): $412E: $03 parameter count $412F: $01 unit number (possibly self- modified above to support running from drive 2) $4130: $4200 address of parameter block $4132: $07 control code: "Download" This is what the firmware reference manual has to say about the "Download" call: --v-- This call is used to download an executable 65C02 routine into the memory resident on the UniDisk 3.5 controller. The address that the routine is loaded into is set by the SetAddress call. The count field must be set to the length of the 65C02 routine to be downloaded. --^-- So $4130 points to $4200, which will contain a length word followed by the actual code to download to the drive. The code is 65c02 code, so I can use the monitor disassembly to read it. *4200. 4200- 70 00 #$70 bytes of code, starting at $4202. But it will be executed on the drive itself, at address $0305. We'll look at it in a moment. First, for completeness, I want to note that this "Download" control call does not auto-execute the code it transfers to the drive. That happens in the next call, at $408A: 408A- 20 5F 40 JSR $405F 408D- [04] 408E- [33 41] 4090- B0 09 BCS $409B $408D is #$04, so we are once again doing a control call. $408E points to the parameter block at $4133. We jump to The Badlands if there's any error. *4133. 4133- .. .. .. 03 01 38 41 05 $4133: $03 parameter count $4134: $01 unit number (possibly self- modified above to support running from drive 2) $4135: $4138 address of parameter block $4137: $05 control code: "Execute" Again from the fine manual: --v-- This call is used to dispatch the intelligent controller in the UniDisk 3.5 device to execute a 65C02 subroutine. The register setup is passed to the routine to be executed from the control list. --^-- The parameter block at $4138 gives the initial values of each register. (This is a 65c02, so it has A, X, and Y registers just like the Apple II its connected to.) *4138. 4138- 06 00 05 05 34 00 05 03 $4138: $0006 block size $413A: $05 A value $413B: $05 X value $413C: $34 Y value $413D: $00 flags value (like PHP/PLP) $413E: $0305 address of code to execute Unsurprisingly, we are executing the code we just downloaded to $0305. Now let's look at that code. ~ Chapter 4 In Which Murphy's Law Never Fails Taking advantage of the fact that this floppy drive runs the same processor as the host Apple II (I AM STILL NOT OVER THAT, BY THE WAY), we can manually move the code to $0305 on the Apple II and use the monitor disassembly to list it. *305<4202.4271M *305L ; save some zero page addresses 0305- A5 73 LDA $73 0307- 8D 4C 05 STA $054C 030A- A5 74 LDA $74 030C- 8D 4D 05 STA $054D ; copy some zero page values 030F- A0 05 LDY #$05 0311- B1 73 LDA ($73),Y 0313- 99 20 05 STA $0520,Y 0316- 88 DEY 0317- D0 F8 BNE $0311 ; overwrite some zero page addresses 0319- A9 4C LDA #$4C 031B- 85 75 STA $75 031D- A9 20 LDA #$20 031F- 85 73 STA $73 0321- A9 05 LDA #$05 0323- 85 74 STA $74 ; call... something in ROM 0325- 20 6A E5 JSR $E56A 0328- 20 62 E1 JSR $E162 032B- EA NOP ; get a raw nibble from the disk (like ; "LDA $C08C,X" on a 5.25-inch floppy) 032C- AD 0E 0A LDA $0A0E 032F- 10 FB BPL $032C ; loop until we find a $D5 nibble 0331- C9 D5 CMP #$D5 0333- D0 F7 BNE $032C ; burn CPU cycles 0335- 48 PHA 0336- 68 PLA ; next nibble must be $B5 (nonstandard) 0337- AD 0E 0A LDA $0A0E 033A- 10 FB BPL $0337 033C- C9 B5 CMP #$B5 ; otherwise loop back to find another ; $D5 nibble 033E- D0 EC BNE $032C ; restore RdAddr hooks in zero page 0340- AD 4C 05 LDA $054C 0343- 85 73 STA $73 0345- AD 4D 05 LDA $054D 0348- 85 74 STA $74 034A- 38 SEC 034B- 60 RTS According to the fine manual, zero page $73 and $74 are part of a "hook table" that jumps to all the hookable routines the drive supports. This includes routines like "RdAddr: find and decode an address field" ($73/$74), "ReadData: find and load a data field in RAM" ($76/$77), and so on. So we're setting the "RdAddr" hook to point to $0520. Well, we just copied 5 bytes of code from ($73) to $0521 (at $030F), but nothing to address $0520. The fine manual lists $0500..$05FF as "free space," so I'm confused. What exactly is supposed to be at $0520? The answer is... a bit shocking. This entire copy protection routine was set up incorrectly. We're not supposed to be downloading code to the address $0305 inside the floppy drive. No, really, we're not supposed to do that. The fine manual says that's part of a buffer for "host communication (format sector buffer I)." I'm not sure what that is, but it is definitely not "free space." The $0500..$05FF page is explicitly listed as "free space." $0305 is not. The root cause, I believe, is an off- by-1 bug in the parameter blocks of the original control calls. This code isn't supposed to be downloaded and executed at $0305; it's supposed to be at $0500. That makes more sense on its face, just because that's the largest block of free space in the drive's memory map. But also, it would make the code itself make more sense. Observe. Suppose, for the sake of argument, that this code ended up at $0500 instead of $0305. Then it would look like this: 0500- A5 73 LDA $73 0502- 8D 4C 05 STA $054C 0505- A5 74 LDA $74 0507- 8D 4D 05 STA $054D 050A- A0 05 LDY #$05 050C- B1 73 LDA ($73),Y 050E- 99 20 05 STA $0520,Y 0511- 88 DEY 0512- D0 F8 BNE $050C 0514- A9 4C LDA #$4C 0516- 85 75 STA $75 0518- A9 20 LDA #$20 051A- 85 73 STA $73 051C- A9 05 LDA #$05 051E- 85 74 STA $74 ; now these two JSR calls are self- ; modified by the code above 0520- 20 6A E5 JSR $E56A 0523- 20 62 E1 JSR $E162 0526- EA NOP 0527- AD 0E 0A LDA $0A0E 052A- 10 FB BPL $0527 052C- C9 D5 CMP #$D5 052E- D0 F7 BNE $0527 0530- 48 PHA 0531- 68 PLA 0532- AD 0E 0A LDA $0A0E 0535- 10 FB BPL $0532 0537- C9 B5 CMP #$B5 0539- D0 EC BNE $0527 053B- AD 4C 05 LDA $054C 053E- 85 73 STA $73 0540- AD 4D 05 LDA $054D 0543- 85 74 STA $74 0545- 38 SEC 0546- 60 RTS If this had been downloaded to, and executed from, address $0500, the RdAddr hook at $73/$74 would have been redirected to $0520. The code at $520 would have been self-modified to be the first 5 bytes of code from the original RdAddr routine, followed by the custom code starting at $0527. That custom code would check for a nonstandard nibble sequence on the next disk read, looping forever if it couldn't find it. (The drive has a "watchdog" timeout, so this would eventually just time out and return an error to the host Apple II.) If it succeeded in finding the custom nibble sequence, it would have restored the RdAddr hook at $73/$74 before returning. All that, combined with the final SmartPort call at $4092... 4092- 20 5F 40 JSR $405F 4095- [01] 4096- [0D 41] Command #$01 is a "read" command. So we would have the drive read a block from the disk, but with the hooked RdAddr routine that points to the protection code at $0520. *410D. 410D- .. .. .. .. .. 03 01 00 4110- 02 10 00 00 $410D: $03 parameter count $410E: $01 unit number (possibly self- modified above to support running from drive 2) $410F: $0200 address of read buffer (in the drive memory) $4111: $000010 block number And *that* SmartPort call would succeed or fail based on whether it found the custom nibble sequence ($D5 $B5) while attempting to read block $10. And *that* is the entire protection: a single custom nibble near block $10. Except none of that happened, because there was an off-by-1 bug in the parameter block of the control call, so the code that self-modified as if were at $0500 ended up being at $0305 instead, and I HAVE LITERALLY NO IDEA HOW THIS EVER WORKED AT ALL, even on an original disk with a compatible UniDisk 3.5 drive. The RdAddr hook ($73/$74) ends up pointing to uninitialized memory at $0520, but because the JSRs at $0325 are never self-modified, it falls through to the actual protection code to check for a custom nibble sequence, but during the Execute call instead of the final ReadBlock call, then restores the RdAddr hook before returning. So the ReadBlock call will always succeed, because by the time it happens, the RdAddr hook has been restored to its original value. I think. Anyway, the entire thing is f---ed. Copy protection barely works in the best of circumstances, which these are not. Downloading code to peripherals is insane in the best of circumstances, which these are not. I will die on this hill.(*) (*) not guaranteed, actual death may vary The protection routine has no side effects. We can simply disable the "JSR $3FEA" (at $384F) and it will happily continue the boot. Using Glen Bredon's "Block Warden," I can search the disk for the offending JSR. The syntax always confuses me, so for future me, the correct sequence is [C]hange device to "Slot 7, Drive 2" [E]dit [Ctrl-S] and enter "$20EA3F" as the search string to search for a hex sequence ; change "JSR" opcode to "BIT" Block $002F, byte $50: 20 -> 2C As an added bonus, we're removed the only thing that tied us to the UniDisk 3.5, so I can boot my cracked copy on my Apple SuperDrive. It's a lovely game. Shame about the protection. Quod erat liberandum. ~ Acknowledgements Many thanks to qkumba for patiently explaining to me how SmartPort firmware calls work, so I could explain it to you. --------------------------------------- A 4am and san inc crack No. 2032 ------------------EOF------------------