/*
    В 1988 году и к  нам пришла эпидемия компьютерных вирусов.  В
связи с этим весьма  актуально научиться с ними  бороться. Данная
программа предназначена для лечения дисков от двух типов  вирусов
и обнаружения наличия  неизвестных вирусов определенного  класса.
По мере обнаружения вирусов новых типов ее довольно просто  можно
приспосабливать для борьбы и с ними.

    Для начала попробуем  дать определение этой  болезни. Назовем
компьютерным вирусом программный фрагмент, обладающий  следующими
свойствами:

    - способность  к размножению  не только  в одной  ЭВМ, но и к
заражению других;

    -  наличие  более  или  менее  значительного   инкубационного
периода, т.е.  интервала от  момента заражения  машины до  какого
либо открытого проявления.

    При  отсутствии  способности  к  размножению  можно  говорить
только о наличии "бомбы"  - скрытой в программе  пакости, которая
должна сработать через некоторое  время, для отмщения врагам,  не
оценившим таланты автора программы.

    Из необходимости  наличия способности  к размножению  следует
обязательная способность вируса заражать программные модули  типа
COM или  EXE. Это  связано с  тем, что  именно модули таких типов
обычно копируют с одной машины на другую. При отсутствии у вируса
инкубационного  периода   сравнительно  легко   можно  определить
источник   заражения   и,   следовательно,   появляется  реальная
опасность для автора вируса.

    Заметим,  что  для  автора  вируса  необходимо  наличие  двух
способностей:
    - уметь программировать на ассемблере и иметь основные
      понятия об операционной системе;
    - уметь получать удовольствие, делая людям пакости.
    К счастью сочетание обоих этих достоинств встречается в людях
достаточно редко.

    Принцип организации любого типа вируса состоит в том, что при
запуске зараженной  программы управление  получает именно  вирус.
Первым  делом  он  обычно  пытается  заразить другие программы, а
затем   обеспечить   правильную   работу   программы.   Заражение
COM-модулей состоит в дописывании себя в конец программного файла
и замене начала (первых трех байт) на команду передачи управления
на собственное начало. При  этом старое значение замененных  трех
байтов сохраняется для  обеспечения правильной работы  зараженной
программы в течении инкубационного периода.

    Для лечения зараженных модулей необходимо только знать, где в
вирусе спрятано старое значение  трех замененных байтов и  какова
длина вируса. Знание прочих подробностей о его организации отнюдь
не необходимо.

    Программа  написана   для  транслятора   "Turbo-C".  Возможно
использование  и  Microsoft-C  версии  не  ниже  5.0.  При   этом
необходимо   заменить   функции    findfirst   и   findnext    на
_dos_findfirst и _dos_findnext, а также имена используемой с ними
структуры и ее полей. Некоторые (но далеко не все) места, которые
необходимо изменить для перехода на MS-C, отмечены в комментариях.

    Общая схема организации  программы заключается в  организации
просмотра  всех  каталогов   (directory)  диска,  проверке   всех
имеющихся COM и EXE файлов  на наличие в них известных  вирусов и
лечение больных. Хотя программа умеет работать только с  модулями
типа  COM,  приходится  проверять  и  модули  с  расширением EXE,
поскольку тип  модуля определяется  не расширением,  а внутренним
форматом. Кроме  того для  всех модулей,  начинающихся с  команды
JMP, вычисляется разность между адресом перехода по этой  команде
и  длиной  файла  и   ведется  статистика  этих  разностей.   При
многократном повторении одинаковых  значений этой разности  можно
предполагать  наличие  вируса.  Впрочем,  не  так  сложно сделать
вирус, который не обнаруживаются таким способом.
*/

#include <dos.h>
#include <dir.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <io.h>
#include <fcntl.h>

/*Дипазон сбора статистики о разностях (пределы длин вирусов)*/
#define MIN_VIRUS 0x0100
#define MAX_VIRUS 0x2000
/*Критическое число повторений разности для тревоги*/
#define critical_no 3

char path[10][13],  /* имена отдельных уровней каталогов */
    full_path[64],  /* буфер для полного пути */
    file_name[64],  /* буфер для полного имени файла */
    disk[4];
/* структуры для функций поиска в каталоге */
/* для MS-C 5.0 вместо ffblk нужно find_t    */
struct ffblk block, dbl[10];
#define dblock (dbl[level])
int fixup=0, and_exe=0, by_date=0;
int *len_index, second_pass=0;

int resident_virus=0; /* обнаружен вирус, остающийся в памяти */
void (far * restart)()=(void far *)0xf000fff0L;

main(int argc, char *argv[])
{
    char *wc, *extention, *extpos;
    int i,fff, dff, w,
        /* счетчики для отчета о проделанной работе */
        total=0, totvir=0, totcorr=0, totkill=0, totrest=0, totqq=0,
        level=0; /* текущий уровень каталога (0 - корневое) */
    wc=NULL;
    for (i=1; i<argc; i++) { /* обработка параметров программы */
        if ( *argv[i] == '/' )  /* ключевой параметр */
        switch( *(argv[i]+1) | 0x20 ) {
            default:
                goto parm_error;
            case 'f':
                fixup=1; break;
            case 'e':
                and_exe=1; break;
        }
        else
            if( wc==NULL ) wc=argv[i];
            else goto parm_error;
    }
    extention = ( and_exe ? "*.*" : "*.com" );
    strcpy(disk,wc);
    if ( (strlen(disk)!=2) || (disk[1]!=':') ) {
parm_error:
        fprintf(stderr,"\n\
Вызов: aidstest d: [/f] [/d] [/e]\n\
где:  d: - имя диска\n\
      /f - исправлять зараженные программы и стирать испорченные\n\
      /e - проверять и .EXE\n");
        exit(1);
    }
    /* размещение и обнуление массива для сбора статистики о разностях */
    len_index=malloc((MAX_VIRUS - MIN_VIRUS) * 2);
    for(i=0; i < MAX_VIRUS - MIN_VIRUS; i++) len_index[i]=0;
    disk[2]='\\';
for_second:
    do { /* этот цикл исполняется для каждого найденного каталога */
        /* формируем полное имя текущего каталога */
        strcpy(full_path,disk);
        for( i=0; i<level; i++ ) {
            strcat(full_path,path[i]);
            strcat(full_path,"\\");
        }
        /* образуем полное имя файла для поиска в каталоге */
        strcpy(file_name,full_path);
        strcat(file_name,extention);
        /* ищем первого представителя интересующих нас файлов */
        /* причем не пропускаем и "спрятанные" (hidden) */
        fff=findfirst(file_name,&block,FA_RDONLY|FA_HIDDEN);
/* MS-C fff=_dos_findfirst(file_name,&block,_A_RDONLY|_A_HIDDEN);*/
        while (fff==0) { /* 0 означает, что нечто найдено */
            /* ищем точку в имени найденного файла */
            extpos = strchr(block.ff_name,'.');
            if( extpos!=NULL && /* если точка найдена и */
                (strcmpi(extpos,".COM")==0 || /* это COM-файл */
                /* или EXE, причем и они нас интересуют */
                (and_exe && strcmpi(extpos,".EXE")==0 ) ) ) {
                /* идем в функцию обработки модуля */
                i = test_virus(full_path,block);
                /* значение на выходе говорит, что произошло */
                if ( i ) {
                    totvir++;
                    if(fixup)
                    switch (i) {
                    case -1: totkill++; break; /* стерли */
                    case -2: totrest++; break; /* не удалось исправить */
                    case  1: totcorr++; break; /* исправили */
                    case  2: totqq++;   break; /* не удалось стереть */
                    default:
                    }
                }
                total++; /* всего проверено файлов */
            }
            fff=findnext(&block); /* ищем следующий файл */
        } /* конец просмотра файлов в одном каталоге */
        /* теперь начинается поиск следующего каталога */
        strcpy(file_name,full_path);
        strcat(file_name,"*.*");
        /* опять ищем с начала каталога, но берем и подчиненные каталоги*/
        dff=findfirst(file_name,&dblock,FA_DIREC|FA_HIDDEN);
        /* пропускаем не каталоги */
        while( ((dblock.ff_attrib & FA_DIREC)==0)
            /* и особые строки "." и ".." */
            || (*(dblock.ff_name)=='.')) {
            /* по концам текущих каталогов поднимаемся выше */
            while ( (dff=findnext(&dblock)) != 0 )
                if(0==level--) break; /* кончился и корневой */
            if ( dff ) break; /* ни одного каталога больше не нашли */
        }
        if ( dff ) break; /* ни одного каталога больше не нашли */
        /* запоминаем имя очередного уровня каталога */
        /* и в переменной level получаем текущую глубину */
        strcpy(path[level++],dblock.ff_name);
    } while(level>0); /* повторение цикла для нового каталога */
    if(second_pass) { /* об этой переменной - ниже */
        printf("Вопросы по тел. Москва - 292.40.76 - Лозинский Д.Н.");
        goto end_of_program;
    }
    printf("\n\
Всего проверено %d\n\
Заражено:       %d\n",total,totvir);
    if(fixup)
     printf("\n\
Исправлено:     %d\n\
Стерто:         %d\n\
Не удалось исправить -\n\
         всего: %d\n\
       опасных: %d\n",totcorr,totkill,totrest+totqq,totqq);
    /* просмотр массива со статистикой разностей */
    for(i=MIN_VIRUS; i<MAX_VIRUS; i++)
    if( (w=len_index[i-MIN_VIRUS]) > critical_no ) {
        printf("Длину хвоста =%X имеет %d модулей\n",i,w);
        second_pass=1; /* статистика неприятная */
    }
    /* пойдем на повторный просмотр диска для вывода */
    /* имен всех подозрительных файлов */
    level=0;
    if (second_pass) goto for_second;
end_of_program:
    if(resident_virus) {
        printf("\n\
    Обнаружен вирус, который делает себя резидентным в памяти\n\
              и заражает все загружаемые программы !\n\
Для надежной очистки машины будет произведена перезагрузка системы.\n\
 Если считаете это излишним, нажмите \"N\", но пеняйте потом на себя!\n");
        i=getch() | 0x20;
        if(i != 'n') {
            *(int far *)0x472L = 0x1234;
            restart();
        }
    }
}

/* Всевозможная работа с одним файлом - проверка, лечение,
   стирание, вывод подозрительных.
   Параметры: полное имя каталога
              структура, используемая в функциях поиска */
test_virus(char *path, struct ffblk block)
{
    unsigned int flen, i,
        jmpadr, vir_adr, vir_len,
        access, drive, exit_code,
        basadr;
    int vir_no, LEN_BAS, BEF_LEN;
    unsigned char filename[64],
            oldstart[3],
            *VIR_BEG,
            *msg="",
            *type_code,
            *wc=NULL;
    long wl;
    /* Это команда ухода на перезагрузку системы, которую пишет в
       начало некоторых модулей один из вирусов. Такие модули
       приходится стирать. */
    char *trup="\xEA\xF0\xFF\x00\xF0";
    /* массивы данных, используемых при опознании вирусов */
    static unsigned len_jmp[]={0x28B, 0x6A7 },
                    len_bas[]={0x8F , 0     },
                    before_len[]={0 , 1     };
    static char *vir_beg[]={"\x51\xBA","\xFA\x8B\xEC\xE8"};

#define no_of_vir 2
#define near_jmp  0xE9

    /* образуем полное имя файла */
    strcpy(filename,path);
    strcat(filename,block.ff_name);

    outfname(filename); /* вывод имени на экран */
    flen = block.ff_fsize; /* длина файла */
    /* если файл только для чтения, пока так его и откроем,
       а иначе сразу и для записи, на случай необходимости исправления */
    access = ( block.ff_attrib & FA_RDONLY ) ? O_RDONLY : O_RDWR;
    drive = open(filename, access | O_BINARY);
    if (drive == -1) return 0; /* открыть не удалось - на выход */
    read(drive, oldstart, 5); /* читаем первые 5 байт */
    if ( !memcmp(oldstart,"MZ",2) ) goto EXE; /* это признак EXE-программ*/
    if ( !memcmp(oldstart,trup,5) ) goto TRUP;/* модуль испорчен вирусом */
    /* если первая команда - не переход, считаем, что вируса нет */
    if ( oldstart[0] != near_jmp ) goto StartNotJmp;
    /* из команды перехода изымаем адрес */
    jmpadr = * (int*) (oldstart+1);
    /* при втором просмотре диска - на проверку по массиву статистики */
    if(second_pass) goto for_second;
    /* первый контроль на известные вирусы по разности длины файла
       и адреса перехода */
    for ( vir_no=0; vir_no<no_of_vir; vir_no++ )
        if ( flen - jmpadr == len_jmp[vir_no] ) goto MayBe;
    goto CLOSE_RET0; /* не опознан */
MayBe: /* судя по длине, это вирус типа vir_no */
    LEN_BAS=len_bas[vir_no]; /* это для опознания одного из типов */
    BEF_LEN=before_len[vir_no]; /* кол-во байтов вируса до точки входа */
    VIR_BEG=vir_beg[vir_no]; /* начальные байты вируса для сравнения */
    /* начало вируса от начала файла (3 из-за способа адресации переходов) */
    vir_adr = jmpadr + 3 - BEF_LEN;
    vir_len = flen - vir_adr; /* длина вируса */
    wc = malloc(vir_len); /* буфер для считывания вируса */
    wl=vir_adr;
    lseek(drive,wl,0); /* переход на начало вируса */
    read(drive,wc,vir_len); /* взяли его в память */
    if ( strncmp(wc+BEF_LEN,VIR_BEG,strlen(VIR_BEG)))
        goto CLOSE_RET0; /* уход при несовпадении начала */
    /* дополнительный контроль специфичный для разных видов */
    switch(vir_no) {
case 0:
        basadr = *(int *) (wc+2) - 0x100;
        if ( flen - basadr != LEN_BAS ) goto CLOSE_RET0;
        type_code="A"; /* тип вируса для сообщений */
        break;
case 1: type_code="B"; /* тип вируса для сообщений */
        break;
    }
        printf("%s - болен (%s)", filename, type_code);
    /* для исправления модуля необходимо восстановить испорченные байты */
    switch(vir_no) {
case 0: /* в вирусе "A" они просто стоят с известным смещением */
        memcpy(oldstart,wc+basadr-vir_adr+0xA,3);
        break;
case 1: /* а вирус "B" нужно предварительно расшифровать */
        resident_virus=fixup; /* кроме того этот вирус остается
                              резидентным в памяти */
        {   int si, di, cx, oldplace=0x2E;
            di=0x23; si=0x123+vir_adr; cx=0x682;
            for (i=di; i<oldplace+3; i++)
                *(int*) (wc+i) ^= cx-- ^ si++;
            memcpy(oldstart,wc+oldplace,3);
        }
        break;
    }
    if(fixup) { /* поступило задание на лечение зараженных модулей */
        /* если открывали только для чтения, переоткроем */
        drive=att_reopen(drive, filename, block.ff_attrib, &msg, access);
        if(drive==-1) { /* не удалось переоткрыть */
            exit_code=-2;
            goto exit_test;
        }
        /* восстанавливаем правильное начало и длину файла */
        lseek(drive, 0L, 0);
        write(drive, oldstart, 3);
        chsize(drive, wl=vir_adr);
        msg = " - ИСЦЕЛЕН";
        close(drive);
        /* если меняли атрибуты файла, восстановить старые */
        if(access == O_RDONLY)
            setfattr(filename,block.ff_attrib);
    }
    else close(drive);
    exit_code=1;

exit_test:
    if( wc != NULL) free(wc);
    if( msg != NULL) printf("%s\n",msg);
    return exit_code;

TRUP: /* файлы, испорченные заменой первых 5 байт исправить невозможно -
         остается их стирать */
    printf("%s - покойник",filename);
    close(drive);
    if(fixup) {
        setfattr(filename, 0); /* стираем все атрибуты
             на случай, если стояло только чтение */
        if ( unlink(filename) ) /* и пробуем стереть */
            { msg = " - СТЕРЕТЬ НЕ УДАЛОСЬ"; exit_code=-2; }
        else { msg = " - ПОХОРОНЕН"; exit_code=-1; }
    }
    goto exit_test;
CLOSE_RET0: /* в начале JMP, но как вирус не опознан */
    vir_len=flen-jmpadr;
    /* собираем статистику */
    if(vir_len>=MIN_VIRUS && vir_len<MAX_VIRUS)
        len_index[vir_len-MIN_VIRUS]++;

StartNotJmp:
    exit_code=0;
    msg=NULL;
    close(drive);
    goto exit_test;

EXE: /* пока такие файлы не обрабатываем */
    goto StartNotJmp;

for_second:
    close(drive);
    /* выдаем на экран имя файла, если он подозрителен по статистике */
    vir_len=flen-jmpadr;
    if  (  vir_len>=MIN_VIRUS
        && vir_len<MAX_VIRUS
        && len_index[vir_len-MIN_VIRUS] > critical_no )
    printf("%s - подозрителен\n",filename);
    return 0;
}

/* замена атрибутов файла */
/* библиотека MS-C 5.0 имеет такую функцию под именем _dos_setfileattr */
setfattr(char *filename, int attrib)
{
    union REGS inregs, outregs;
    struct SREGS segregs;
    inregs.x.ax = 0x4301;
    inregs.x.cx = attrib;
    inregs.x.dx = FP_OFF(filename);
    segregs.ds = FP_SEG(filename);
    intdosx( &inregs, &outregs, &segregs);
}

/* В зависимости от первого параметра сохраняется (1) или
   восстанавливается (0) позиция курсора. Позиции хранятся
   в стеке на 10 значений, хотя для данной программы нужно
   только одно. При сохранении позиции и ненулевом втором
   параметре стирается строка, содержащая курсор.
   Для MS-C в этой функции придется сделать много изменений */
CurSavRest(int save, int clear)
{   static int pointer=0, cstack[10];
    union REGPACK regs;
    if(save) {
        regs.r_ax=0x300;
        regs.r_bx=0;
        intr(0x10, &regs);
        cstack[pointer]=regs.r_dx;
        if(clear) {
            regs.r_cx=regs.r_dx;
            regs.r_dx &= 0xFF00;
            regs.r_dx += 79;
            regs.r_ax = 0x600;
            regs.r_bx = 0x700;
            intr(0x10, &regs);
        }
        if(pointer<9) pointer++;
    } else {
        if(pointer>=0) pointer--;
        regs.r_ax=0x200;
        regs.r_bx=0;
        regs.r_dx=cstack[pointer];
        intr(0x10, &regs);
    }
}

/* вывод имени файла на экран с возвратом курсора на старое место */
outfname(char *filename)
{
    CurSavRest(1,1);
    printf(filename);
    CurSavRest(0,1);
}

/* если файл был открыт только для чтения, у него меняются атрибуты
   и он открывается заново на чтение и запись */
int att_reopen(int drive, char *filename, char attrib, char **msg, char acc)
{
    if(acc == O_RDONLY) {
        close(drive);
        setfattr(filename, attrib & ~(FA_RDONLY|FA_SYSTEM));
        drive=open(filename, O_RDWR | O_BINARY);
        if (drive==-1) *msg = "!!! ОШИБКА ДОСТУПА !!!";
    }
    return drive;
}

/*
    Относительно    несложно    написать    программу,    которая
обнаруживает появление  на диске  почти любого  вируса. Для этого
нужно   воспользоваться   тем,   что   при   заражении  программы
необходимо, чтобы вирус  начинал работу до  зараженной программы.
При этом практически во всех случаях необходимо внести  некоторые
изменения в начале программного модуля.

    Для программ формата COM вместо замены первых трех байт,  как
это  происходит  в  вирусах,  с  которыми  умеет  бороться данная
программа, автор выруса может воспользоваться тем, что почти  все
программы формата COM сами начинаются с команды перехода. В  этом
случае подстановка  перехода в  вирус может  производиться в  том
месте, куда делается первый переход.

    Для программ  в формате  EXE возможность  для вируса обойтись
без  изменения  первых  32  байт  программного  файла,  в которых
содержится основная управляющая информация, представляется весьма
проблематичной.

    Из этих фактов следует идея организации программы слежения за
проникновением на диск какого либо вируса. Программа должна вести
специальный  файл,   содержащий  для   всех  программных   файлов
параметры, которые заведомо должны измениться при заражении.  Эти
параметры могут, например, включать длину файла, сумму первых  32
байт  и,  для  COM-программ,  сумму  нескольких  байт  за  первым
переходом. Эта  информация должна  ежедневно сличаться  с текущим
состоянием  диска  и  обо  всех  замеченных  расхождениях  должно
выдаваться сообщение.
*/
