/*
	kv - removes the IRQ virus from an executable file.
	compile with Manx C - 16 bit mode ok

	Routine IRQ_Kill() does the work, the rest is a vehicle

	This program will also detect one varient of the Lamer virus in memory
	and deactivate it.  

	Also checks for the BGS9 virus

			Djj  8/25/89
*/
#include <exec/types.h>
#include <exec/exec.h>
#include <libraries/dos.h>
#include <intuition/intuition.h>
#include <devices/trackdisk.h>
#include <exec/execbase.h>
#include <exec/resident.h>
#include <stdio.h>


#define VERSION  "KV  ver 2.0  09/01/89  Djj"

#define	VTAG	 0x00000109L		/* virus hunk length */
#define VSIG1    0x0000FFFEL		/* part of virus signature */
#define VSIG2    0x61000000L		/* part of virus signature */

#define	BEGIN	 OFFSET_BEGINNING
#define END		 OFFSET_END
#define OLD		 MODE_OLDFILE
#define NEW		 MODE_NEWFILE

#define SLONG	 ((long)(sizeof(long)))

#define	OK        1			/* no error */

#define	NOFILE   -1			/* can't find file */
#define	SEEKERR  -2			/* seek error on file */
#define ALLOCERR -3			/* can't allocate memory */
#define READERR	 -4			/* error reading file */
#define VIRUSNF  -5			/* virus not found in file */
#define WRITERR  -6			/* error writing to file */
#define OPENERR  -7			/* error opening file for write */
#define NOTEXE   -8			/* not an executable file */

#define DEADXENO -9			/* disabled XENO found */
#define GOTHIM	 OK			/* removed virus from file */

static UBYTE txt[40];
static long inp, *buff, size;
extern long Open(), Seek(), Read(), Write(), *AllocMem();
long DOSBase, IntuitionBase, OpenLibrary();

int	Xeno = FALSE;

struct IntuiText Msg3 = { 3, 1, JAM2, 10, 30, 0,(UBYTE*)NULL, NULL  };
struct IntuiText Msg2 = { 2, 1, JAM2, 10, 20, 0,(UBYTE*)NULL, &Msg3 };
struct IntuiText Msg1 = { 2, 1, JAM2, 10, 10, 0,(UBYTE*)NULL, &Msg2 };

struct IntuiText OKText  = { 3, 1, JAM2, 6, 3, 0,(UBYTE*)" OK ", NULL };


main(argc,argv)
char **argv;
{
	static char opts[] = "aAiIbBlL", *strchr();
	char   *posn, *temp;
	int lamer = 0, irq = 0, bgs = 0, cnt = 1;
	int  indx, i, args;


	if(argc < 2)
		usage(argv[0]);

	IntuitionBase = OpenLibrary("intuition.library",0L);
	DOSBase = OpenLibrary("dos.library",0L);

	args = argc;
	for(i = 1; i < argc; i++)
	{
		temp = argv[ i ];
		if(*temp == '?')
			usage();
		if(*temp++ == '-')
		{
			args--;
			while(*temp)
			{
				if((posn = strchr(opts,*temp)) == 0)
				{
					printf("Unknown option %c\n",*temp++);
					continue;
				}
				indx = posn - opts;
				switch(indx)
				{
					case 2: case 3:
									irq = 1; temp++;
									break;
					case 4: case 5:
									bgs = 1; temp++;
									break;
					case 6: case 7:
									lamer = 1; temp++;
									break;
					case 0: case 1:
									bgs = lamer = 1; temp++;
									break;
				}
			}
		}
		else
		{
			CheckIRQ(argv[i]);
		}
	}


	if(lamer)
		CheckLamer();

	if(bgs)
		Check_BGS9();
	
	exit(cleanup(1));
}

CheckIRQ(dev)
char *dev;
{
	int err;
	char *pat = 0, *name = 0, *scdir(), *malloc(), ch;
/*
	scan argument list for filenames.  Use each name as input to the
	wildcard expander.  If a name ends with `:' or `/' then it's a
	device or directory.  As a favor to the user, append the wildcard
	`*' character to the end of that name.
*/
	printf("Checking for IRQ and Xeno virus on %s\n",dev);
	if(pat) free(pat);
	pat = malloc(strlen(dev)+1);	/* leave room for `*' if needed */

	if(pat == 0)
	{
		puts("Out of memory");
		exit(cleanup(20));
	}
	strcpy(pat,dev);		/* get filename from command line */

	ch = pat[ strlen(pat)-1 ];
	if(ch == ':' || ch == '/')
		strcat(pat,"*");

/*
	do this until no more names can be squeezed out of this argument
*/
	while(1)
	{
		if((name = scdir(pat)) == NULL)
			break;	
		err = IRQ_Kill(name);	/* check for and possibly remove virus */

		switch(err)
		{
		case GOTHIM:
						printf("\2337m");
						printf("Found and disabled the %s virus in \"%s\"!\n",
								(Xeno) ? "XENO" : "IRQ", name);
						printf("\2330m");

						if(!Xeno)
						{
							DisplayBeep(0L);
							Msg1.IText = (UBYTE *)"Found the IRQ virus in file";
							Msg3.IText = (UBYTE *)"IRQ virus removed";
						}
						else
						{
							Msg1.IText = (UBYTE *)"Found the XENO virus in file";
							Msg3.IText = (UBYTE *)"XENO virus disabled";
						}
						Msg2.IText = txt;
						sprintf(txt,"   %s",name);

						if(!Xeno)
							putmsg();
						else
							Delay(20L);
						break;
		case NOFILE:   	printf("\tcan't find \"%s\"\n",name);
						break;
		case SEEKERR:  	printf("\tseek error on \"%s\"\n",name);
						break;
		case ALLOCERR: 	printf("\tcan't allocate memory\n");
						break;
		case READERR:	printf("\terror reading \"%s\"\n",name);
						break;
		case VIRUSNF:  	printf("\"%s\" was OK\n",name);
						break;
		case WRITERR:  	printf("\terror writing to \"%s\"\n",name);
						printf("\tFile \"%s\" is corrupt...replace it\n",name);
						break;
		case OPENERR:  	printf("\terror opening \"%s\" for write\n",name);
						break;
		case NOTEXE:   	printf("\t\"%s\" is not an executable file\n",name);
						break;
		case DEADXENO:	printf("found disabled XENO virus in \"%s\"\n",name); 
		}
	}
}

CheckLamer()   
{
	puts("Checking for Lamer virus");
	Check_Memory_Lamer();
	Check_Disk_Lamer();
}

cleanup(val)
int val;
{
	if(inp)
	{
		Close(inp);
		inp = 0;
	}
	if(buff)
	{
		FreeMem(buff,size);
		buff = 0;
	}
	return val;
}


IRQ_Kill(fname)
char *fname;
{
	int   i = 0;
	long  fhunk, lhunk, nhunk, val, len;

	inp = 0L;  buff = 0L;

	if((inp = Open(fname,OLD)) == 0)	/* open the desired file */
		return NOFILE;

	Read(inp,&val,SLONG);				/* get the first long word */
	if(val != 0x000003F3L)				/* check for executable file */
		return cleanup(NOTEXE);

	Seek(inp,0L,END);				/* seek to end of file */
	size = Seek(inp,0L,BEGIN);		/* rewind the file to get size */
	if(size <= 0L)
		return cleanup(SEEKERR);

	if((buff = AllocMem(size,0L)) == 0)	/* mem buffer for file */
		return cleanup(ALLOCERR);

	if(Read(inp,buff,size) != size)	/* read entire file into buffer */
		return cleanup(READERR);

	nhunk = buff[ 2 ];		/* number of hunks in file */
	fhunk = buff[ 3 ];		/* first hunk */
	lhunk = buff[ 4 ];		/* last hunk */

/*
	the IRQ virus inserts his code as the first hunk in the file
	we can find the first hunk in the 6th longword of the file
*/
	/* find the start of the virus hunk */
	i = 5 + lhunk - fhunk + 1;

	Xeno = FALSE;

	if(buff[5] != VTAG)
	{
		if(buff[ i + 0x116 ] != 'astf')
			return cleanup(VIRUSNF);
		else
		{
			Xeno = TRUE;
			if(buff[i+2] == 0x60000462)
				return cleanup(DEADXENO);
		}
	}

/*
	The virus checks the first hunk for it's signature to prevent
	reinfecting an already infected program.  It actually checks for
	0xFFFE6100 which is the lower half of a move.m instruction and
	the upper half of a bsr instruction.  Here we do the same.
    i points to the virus code hunk length.
*/
	if(!Xeno)
		if( ((buff[ i+2 ] & 0x0000FFFF) != VSIG1) 
	     || ((buff[ i+3 ] & 0xFFFF0000) != VSIG2) )
			return cleanup(VIRUSNF);

/*
	close the file and reopen it with write access
	mode NEW will effectivly delete the old file contents so if a
	file error occurs before we finish, the file will be corrupt
*/
	Close(inp);  inp = 0;

	if((inp = Open(fname,NEW)) == 0)
		return cleanup(OPENERR);

	if(!Xeno)	/* readjust program header info */
	{
		buff[2] -= 1;
		buff[4] -= 1;
/* write new header */
		if(Write(inp,buff,5L*SLONG) != 5L*SLONG)
			return cleanup(WRITERR);

		if(Write(inp,&buff[6],nhunk*SLONG) != nhunk*SLONG)
			return cleanup(WRITERR);

/*
	subtract length of virus hunk and overhead from file size
	i was calculated earlier
*/
		i = i + 9L + VTAG;
		size = (size/SLONG - i)*SLONG;

/* and write old code out to file, ignoring virus hunk */
		if(Write(inp,&buff[i],size) != size)
			return cleanup(WRITERR);

/* we're done, the file is clean and intact */
		return cleanup(GOTHIM);
	}
	else		/* Xeno virus */
	{
		buff[i+2] = 0x60000462;

/* now fix the code segment length and write it out */

		if(Write(inp,buff,size) != size)
			return cleanup(WRITERR);

/* we're done, the file is clean and intact */
		return cleanup(GOTHIM);
	}
}

usage(name)
char *name;
{
	printf("%s\n\n",VERSION);
	printf("%s will detect and remove one type of `Lamer' and `BGS' virus \n",name);
	printf("from all floppies in the system.  It will also detect and remove\n");
	printf("IRQ virus V41.0 from executable files and detect and disable the\n");
	printf("XENO virus in executable files\n\n");
	printf("Usage: %s -LIBA { filename ... }\n\n",name);
	printf("\t-L or -l       check for Lamer virus on all floppies\n");
	printf("\t-B or -b       check for BGS-9 virus on all floppies\n");
	printf("\t-I or -i NAME  check for IRQ and Xeno virus on NAME\n");
	printf("\t-A or -a       do all of the above\n\n");
	printf("\t`*' (unix style) wildcards allowed\n");
	printf("\tex. %s -i C:*  or  %s -i C:  will check all files in C:\n",
		name,name);
	printf("\t\tfor the IRQ and Xeno virus\n");
	printf("\t    %s DF0:C/ will check all files in DF0:C\n",name);
	exit(cleanup(20));
}

#define LAMER_ID	"clist 33.80"

Check_Memory_Lamer()
{
	struct ExecBase **ExecPtr = 4;
	APTR *KTagPtr = (APTR *)(*ExecPtr)->KickTagPtr;
	APTR Lamer, TestKTag();

	while(*KTagPtr)
	{
/*
	If hi bit is set, then this is a pointer to a KickTag array.  Reset
	the bit and use the resulting address to get the address of the
	next KickTag array.
*/
		while((ULONG)*KTagPtr & 0x80000000)
			KTagPtr = (APTR *)((ULONG)*KTagPtr & 0x7FFFFFFF);

/*
	if the Lamer is found, invalidate the KickTag element here by
	changing the KickTag element to a pointer to a KickTag array which
	points to the next element.  A cheap shot, but effective
*/
		if(Lamer = TestKTag( *KTagPtr ))
		{
			if(KillLamer(Lamer))
			{
				*KTagPtr = (APTR)((ULONG)(KTagPtr+1) | 0x80000000);

				SumKickData();		/* do the real SumKickData */

				Msg1.IText = txt;
				sprintf(txt,"Lamer virus found at %08lx",Lamer);
				putmsg();
				return;
			}
		}
		KTagPtr++;
	}
}

/*
	This is supposed to compute the correct Kick memory checksum to
	allow the users RAD: disk to recover on the next reboot.
	The SumKickData vector was recovered from the virus and reinstalled
	in the execbase structure by KillLamer()
*/

SumKickData()
{
#asm
	move.l	4,a6
	jsr	-$264(a6)			;calculate checksum
	move.l	d0,$22A(a6)		;store it
#endasm
}

KillLamer(addr)
APTR addr;
{
#define	RTS	0x70014E75	/* moveq.l  #1,d0   rts */
	struct ExecBase **ExecPtr = 4;

/*
	I think that the Lamer is in memory.  I'm going to test several
	locations within the virus body to be sure that it is the exact
	virus I'm familiar with.  The test array has memory offsets from
	addr and the contents I expect to see there.
*/
static	APTR test[] = {
		0x0A6, 0x2F3A0EEC,		/* SumKick routine */
		0x166, 0x2F3A0E3C,		/* DoIO routine */
		0x246, 0x2F3A0D64,		/* TDRead routine */
		0x2B8, 0x287A0D0C,		/* Trigger point */
		0x5AE, 0x2C780004		/* Disk Trasher */
};

/*
	I'm going to pull the vipers fangs by placing RTS instructions in
	key areas.  One area is his Resident Initialization vector - that'll
	stop him from living through a re-boot (provided he's not on the
	boot disk that is)
*/
static	APTR rts[] = {
		0,				/* Keep from initializing on reset */
		0x2B8,			/* disable one trigger point */
		0x5AE,			/* stop the disk trasher */
		0x6D8,			/* stop the alerts */
		0x73C			/* his ReBoot exit */
};

	int i;
	APTR ptr;
	UBYTE *vec, *mem;
	ULONG *vect, FindName();

	for(i = 0; i < sizeof(test)/sizeof(ULONG); i+= 2)
	{
		ptr = addr+test[ i ];
		if( *ptr != test[ i+1 ] )
		{
			if(*ptr != RTS)
			{
				Msg2.IText = (UBYTE *)"I don't know this one";
				Msg3.IText = (UBYTE *)"virus NOT removed!";
				return 0;
			}
			else
			{
				Msg3.IText = (UBYTE *)"It's OK! It's been deactivated";
				return 0;
			}
		}
	}
	for(i = 0; i < sizeof(rts)/sizeof(ULONG); i++)
	{
		ptr = addr+rts[ i ];
		*ptr = RTS;
	}

/*
	Get the SumKickData vector from the virus and reinstall it in the
	ExecBase jump table.
*/
	vec = (UBYTE *)(*ExecPtr);
	vect = (ULONG *)(vec - 0x264+2);	/* ExecBase offset */
	ptr = addr+0x3E5;					/* Offset from Lamer Init vector */
	*vect = (ULONG)*ptr;				/* restore vector */

/*
	Get the Trackdisk read vector from the virus and reinstall it
*/
	vect = (ULONG *)(FindName(&(*ExecPtr)->DeviceList,TD_NAME)-0x1c);
	ptr = addr + (0xFAC/4);
	*vect = (ULONG)*ptr;

	Msg3.IText = (UBYTE *)"Virus removed!";
	return 1;
}

/*
	Test the Resident structure ID string to see if it matches the one
	the Lamer uses.  Return the structures Init vector if true, NULL if
	not
*/
APTR TestKTag( res )
struct Resident *res;
{
	if(strncmp(LAMER_ID,res->rt_IdString,strlen(LAMER_ID)) == 0)
		return res->rt_Init;
	return (APTR)NULL;
}

struct Port *diskport = 0, *CreatePort();
struct IOExtTD *diskreq = 0, *CreateExtIO();

#define SEC_SIZE		512L
#define ROOT			880L
#define HASHCHAIN		124
#define WRITE_PROTECTED -1
#define NO_DISK			-2

UBYTE *diskdata = 0;
UBYTE invisible[] = "DF0:\240\240\240\240\240";
UBYTE newfile[] = "DF0:DANGERVIRUS";

WriteBlock(block)
long block;
{
	LONG offset;

	MotorOn();

	offset = block * SEC_SIZE;

	diskreq->iotd_Req.io_Length  = SEC_SIZE;
	diskreq->iotd_Req.io_Data    = (APTR)diskdata;
	diskreq->iotd_Req.io_Command = CMD_WRITE;
	diskreq->iotd_Req.io_Offset  = offset;

	DoIO(diskreq);

	if(diskreq->iotd_Req.io_Error != 0)
		printf("I/O error %d\n",diskreq->iotd_Req.io_Error);

	diskreq->iotd_Req.io_Command = ETD_UPDATE;
	DoIO(diskreq);

	MotorOff();
}

ReadBlock(block)
long block;
{
	LONG offset;

	MotorOn();

	offset = block * SEC_SIZE;

	diskreq->iotd_Req.io_Length  = SEC_SIZE;
	diskreq->iotd_Req.io_Data    = (APTR)diskdata;
	diskreq->iotd_Req.io_Command = CMD_READ;
	diskreq->iotd_Req.io_Offset  = offset;

	DoIO(diskreq);

	if(diskreq->iotd_Req.io_Error != 0)
		printf("I/O error %d\n",diskreq->iotd_Req.io_Error);

	MotorOff();
}
  
MotorOn()
{
	diskreq->iotd_Req.io_Length  = 1;
	diskreq->iotd_Req.io_Command = TD_MOTOR;
	DoIO(diskreq);
}
 
MotorOff()
{
	diskreq->iotd_Req.io_Length  = 0;
	diskreq->iotd_Req.io_Command = TD_MOTOR;
	DoIO(diskreq);
}
  
CheckDOS()
{
	ReadBlock(0L);
	if(diskdata[0] == 'D' && diskdata[1] == 'O' && diskdata[2] == 'S')
		return 1;
	return 0;
}

DiskStatus()
{
	diskreq->iotd_Req.io_Flags   = 0;
	diskreq->iotd_Req.io_Command = TD_PROTSTATUS;

	DoIO(diskreq);

	if(diskreq->iotd_Req.io_Error == TDERR_DiskChanged)
		return NO_DISK;

	if(diskreq->iotd_Req.io_Actual)
		return WRITE_PROTECTED;

	return OK;
}

Check_Disk_Lamer()
{
	int i;

	for(i = 0; i < 4; i++)
	{
		if(OpenDiskResource(i) == 0)
			return;

		CheckDisk(i);

		CloseDiskResource();
	}
}
 
OpenDiskResource(num)
int num;
{
	struct ExecBase **ExecPtr = 4;
	ULONG td, FindName();

	td = FindName(&(*ExecPtr)->DeviceList,TD_NAME);
	diskdata = (UBYTE *)AllocMem(SEC_SIZE,(LONG)(MEMF_CHIP | MEMF_CLEAR));

	diskport = CreatePort(0L,0L);
	if(diskport == NULL || diskdata == NULL)
		exit(cleanup(20));

	diskreq = CreateExtIO(diskport,(long)sizeof(struct IOExtTD));
	if(diskreq == NULL)
	{
		DeletePort(diskport);
		exit(cleanup(20));
	}

    if(OpenDevice(TD_NAME,(LONG)num,diskreq,0L))
		return 0;

	return 1;
}

CloseDiskResource()
{
	if(diskreq)
	{
		CloseDevice(diskreq);
		DeleteExtIO(diskreq,(long)sizeof(struct IOExtTD));
		DeletePort(diskport);
	}

	if(diskdata)
		FreeMem(diskdata,SEC_SIZE);

	diskreq  = 0;
	diskdata = 0;
}

CheckDisk(num)
int num;
{
	int i;
	long *buff, Virus = 0, block, link = 0, before = 0, after = 0;
	unsigned long lock, Lock(), CheckSum();
	char drive[5];

	if(DiskStatus() == NO_DISK || CheckDOS() == 0)
		return;

	invisible[2] = newfile[2] = 0x30 + num;

	if((lock = Lock(invisible,ACCESS_READ)) == DOSFALSE)
		return;
	UnLock(lock);

	strncpy(drive,invisible,4); drive[4] = 0;

	if(DiskStatus() == WRITE_PROTECTED)
	{
#ifdef CLI
		printf("CAUTION! Lamer Virus found on drive %4s\n",drive);
		printf("Disk is write protected.  Make disk writable\n");
#else
		Msg1.IText = txt;
		sprintf(txt,"Lamer Virus found on %4s",drive);
		Msg2.IText = (UBYTE *)"Disk is write protected";
		Msg3.IText = (UBYTE *)"Make disk writable";
		putmsg();
#endif
		return;
	}

	if((lock = Lock(newfile,ACCESS_READ)) != DOSFALSE)
	{
		UnLock(lock);
		DeleteFile(newfile);
	}

	if(Rename(invisible,newfile) == DOSFALSE)
	{
		printf("WARNING - attempt to rename virus has failed\n");
		printf("The virus hides in an invisible file on drive %4s\n",drive);
	}
	else
	{
		printf("NOTICE! - Lamer virus found on drive %4s\n",drive);
		printf("The virus has been renamed to %4sDANGERVIRUS.\n",drive);
		printf("Please delete this file and edit your Startup-Sequence file\n");
		printf("to remove the invisible filename found on the first line\n");
	}
/* virus is present */
	ReadBlock(ROOT);

	buff = (long *)diskdata;

	Virus = buff[ 15 ];

	for(i = 14; i > 6; i--)
		if(before = buff[ i ])
			break;

	if(before == 0)
		return;

	for(i = 16; i < 81; i++)
		if(after = buff[ i ])
			break;

	if(after == 0)
		return;

	block = before;
	do {
		ReadBlock(block);

		link = buff[ HASHCHAIN ];

		if(link == after)
		{
			buff[ HASHCHAIN ] = buff[ 5 ] = 0;
			buff[ 5 ] = CheckSum(buff);
			WriteBlock(block);
			break;
		}
	} while(block = link);
}

unsigned long CheckSum(block)
unsigned long block[];
{
	int cnt;
	unsigned long sum = 0;
	for(cnt = 0; cnt < 128; cnt++)
		sum += block[ cnt ];
	return (~sum + 1);
}

putmsg()
{
	AutoRequest(NULL,&Msg1,NULL,&OKText,NULL,NULL,300L,80L);
}

UBYTE BGS_name[] = "DF0:devs/\240\240\240   \240   \240";
UBYTE newname[] = "DF0:devs/OLD_PROGRAM.BGS9";


Check_BGS9(num)
{
	int i;

	puts("Checking for BGS virus");

	for(i = 0; i < 4; i++)
	{
		if(OpenDiskResource(i) == 0)
			return;

		CheckDiskBGS(i);

		CloseDiskResource();
	}
}

CheckDiskBGS(num)
int num;
{
	ULONG	file;
	UBYTE	drive[5];
	int i;

	if(DiskStatus() == NO_DISK || CheckDOS() == 0)
		return;

	BGS_name[2] = num + '0';
	if((file = Lock(BGS_name,ACCESS_READ)) != DOSFALSE)	
	{
		UnLock(file);
		strncpy(drive,BGS_name,4); drive[4] = 0;
		sprintf(txt,"BGS virus found on %s",drive);
		Msg1.IText = txt;

		if(DiskStatus() == WRITE_PROTECTED)
		{
			Msg2.IText = (UBYTE *)"Disk is write protected";
			Msg3.IText = (UBYTE *)"Make disk writable";
			putmsg();
			CloseDiskResource();
			return;
		}
		newname[2] = num + '0';
		if(Rename(BGS_name,newname) == DOSFALSE)
		{
			Msg3.IText = (UBYTE *)"Rename failed";
		}
		putmsg();

		printf("You must now examine %sS/Startup-Sequence",drive);
		puts(" and note the name of the first");
		puts("executable file.  That file contains the BGS virus\n");
		puts("DELETE that file and replace it with a know good copy");
		puts("You might also want to remove or rename the file located in");
		printf("%s/devs.  It contains the original program replaced",drive);
		puts(" by the BGS virus");
	}
}
