Написание своего антивируса на Java

Java   21 Декабрь 2012  Автор статьи:  

К сожалению, данная тема далеко не для новичков, поэтому, если вы не знаете основ Java, то лучше прочитайте урок про написание своего калькулятора. В рамках данной статьи, рассматривается обнаружение вирусов, основанное на сигнатурах вирусов, а затем реализуется простой антивирусный сканер, основная цель которого показать элементы теории на практике. Мы реализуем антивирусный сканер, который будет значительно упрощен по сравнению с любыми современными вариантами сканера, но в нем присутствуют все необходимые детали для нормального функционирования. В сканере реализованы: база данных, с возможностью добавления новых сигнатур в базу; сканер, который реализует поиск вредоносных сигнатур в PE файлах. Для реализации сканера был выбран язык программирования Java в связи с достаточным объемом проекта и постепенным развитием проекта. Основная сложность данной задачи заключается в написании быстрого сканера, который позволил бы сканировать достаточные объемы информации. Решение данной задачи производиться с помощью поиска вирусов только в исполняемых файлах и использования быстрого алгоритма сравнения сигнатуры.

Обнаружение вирусов и создание сигнатур

Антивирусный сканер в данной статье построен на методе обнаружения сигнатур. Это такой метод, при котором антивирусная программа просматривает каждый файл, сравнивает его с уже имеющейся базой данных и при совпадении участков кода делает одно из 3 действий:

  • Удаляет файл
  • Перемещает в карантин
  • Лечит файл, удаляя из него код вируса

Для достижения результатов при использовании данного метода необходимо своевременно обновлять базу сигнатур, чтобы избежать заражения своего компьютера. Данный процесс осложняется тем, что придуманы полиморфные вирусы, которые изменяют свою сигнатуру, не давая, таким образом, сканеру обнаружить себя. Сигнатуры создаются в процессе длительного анализа несколько копий одного вируса, при этом они должны содержать уникальные строки вредоносной программы, чтобы максимально уменьшить число ложных срабатываний. Автоматические методы создания сигнатур также наталкиваются на проблему обнаружения полиморфных вирусов. Достоинство данного метода поиска вредоносных программ в высокой вероятности обнаружения вирусной атаки и малой долей ложных срабатываний. Недостатки данного метода в неспособности выявить новые атаки, относительной беззащитности перед полиморфными вирусами, зависимости от частоты обновления баз данных, трудности анализа вируса.
Вредоносная программа – это файл и набор действий, который она производит в системе. В сигнатуру входят все характерные черты вируса, такие как последовательность байт, влияние на операционную систему, способ лечения вируса, если такой известен, и другие. В своей программе я для работы с сигнатурами создал специальный класс SegmentCode. Он, как следует из его названия, служит для работы с сегментами кода. В нем я храню последовательность байт, ее длину и название вируса. Для создания экземпляра класса предусмотрено два конструктора. Первый из них создает экземпляр класса на основе последовательности байт и названии вируса. Этот конструктор служит для работы с базой данных и загрузки из нее сигнатур вирусов. Второй конструктор служит для загрузки сегментов кода, которые в дальнейшем программа собирается проверять. Так как класс приспособлен для хранения последовательности байт, которые были взяты либо из базы, либо из файла, то в нем существует дополнительные методы, которые помогают для работы с ним. Публичными являются три метода, получения последовательности байт, поиск сегмента кода в сегменте и переопределенный метод класса Object toString(), который возвращает название сегмента кода и его длину. В классе существует дополнительный закрытый метод makeSegment(), который используется для того, чтобы прочитать последовательность байт из файла, при каждом вызове осуществляется автоматическая проверка готовности класса к работе, и если он не готов к работе, тогда вызывается этот метод.
Для проверки работоспособности программы решено было создать уникальную сигнатуру. За вирусный код был условно взят следующий код на Delphi:

1
2
Write(‘I am virus’);
Sleep(1000);

Для выделения последовательности байт соответствующую данному вирусу, было создано два exe файла, которые были «заражены» данными командами, также было создан еще один файл, который не содержал вирусный код. Для создания последовательности байт, соответствующей данному вирусу, была написана программа, которая находила все общие подпоследовательности, т.о. чтобы их множество не пересекалось, и подпоследовательности, которые можно было бы объединить в одну подпоследовательность были объединены, а из этого множества с помощью третьего файла были удалены подпоследовательности, которые присутствовали в нем. Таким образом я получил сигнатуру вируса состоящую из двух команд: вывести сообщение на экран и заснуть.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
import java.io.IOException;
import java.io.RandomAccessFile;
public class SegmentCode {
    //имя сегмента кода
    private String name;
    private boolean ready;
    //размер сегмента кода
    private long sizeofSegment;
    //байт массив сегмента кода
    private byte[] mas;
    private long pointer;
    private RandomAccessFile nameOfFile;
    //конструктор сегмента кода от размера его,смещение, RandomAccessFile
    //и название сегмента
    public SegmentCode(long length, long pointer, RandomAccessFile nameOfFile, String name) throws IOException {
        this.name = name;
        this.sizeofSegment = length;
        this.pointer = pointer;
        this.nameOfFile = nameOfFile;
        ready = false;
    }
    public SegmentCode(byte[] mas,String name) {
        this.name=name;
        this.mas=mas;
        this.sizeofSegment=mas.length;
        ready=true;
    }
    private void makeSegment() throws IOException {
        if(ready) return;
        nameOfFile.seek(pointer);
        mas = new byte[(int) sizeofSegment];
        nameOfFile.read(mas);
    }
    public boolean  find(SegmentCode sign){
        boolean ans=false;
        try {
            ans=this.find(sign.getmas());
        } catch (IOException e) {
            e.printStackTrace();
        }
        return ans;
    }
    public byte[] getmas(){
        return mas;
    }
    private boolean find(byte[] mass) throws IOException {
        if (!ready) makeSegment();
        return VirusLibrary.KMP(mass,mas);
    }
    public String toString(){
        if(!ready) try {
            makeSegment();
        } catch (IOException e) {
            e.printStackTrace();
        }
        String bla= "Название сегмента="+name+" Размер сегмента"+sizeofSegment+"\n";
        return sizeofSegment+" ";
    }
}
Антивирусные сканеры

Антивирусные программы, выявляющие известные компьютерные вирусы называют сканерами. Сканеры разделяют на транзитные, т.е. запускаемые периодически и резидентные, которые постоянно находятся в оперативной памяти. Для эффективной работы сканера необходимо обновлять антивирусные базы. Сканер не может находить новые вирусы, потому что просто сравнивает файл с сигнатурой вируса. В связи с проблемой ложных срабатываний сканеров, обычно файл приводят к каноническому виду, прежде чем выполнять сравнение сигнатур, т.е. обнуляют все переменные, но в своей программе я не привожу к каноническому виду, потому, что сравниваю просто сегменты кода, без сравнения данных. Кроме сканера современные антивирусы используют другие модули для повышения эффективности борьбы с вирусами. Эти модули используют эвристический анализ или проактивную защиту. Современные сканеры, прежде чем сравнивать последовательности байт, используют дополнительные методы подсчета различных сумм и т.п. В своем сканере я не стал использовать дополнительные методы, ограничившись лишь проверкой последовательности байт. Конечно, такой алгоритм не может претендовать на высокую скорость работы, но является достаточно надежным, и при сканировании относительно небольших объемов данных имеет право на существование. Сканер, реализованный для этой статьи, является транзитным, потому что основное внимание уделяется поиску вредоносных программ, а не способу работы сканера. Для реализации обхода файлов, управление загрузки библиотеки, и других управляющих действий я создал класс ScanSystem, который состоит только из статичных методов служащих для управления программой. Основным является метод scan, который осуществляет основную работу сканера. Этот метод принимает на вход файл или папку,а дальше запускается метод run1(), который реализует обход файлов для подсчета общего количества файлов, которые нужно будет просканировать для оценки времени работы сканера. Т.е. если в метод передана папка, то он заходит в нее и рекурсивно запускается от всех ее элементов, если же это файл, то он просто запускает процедуру сканирования файла scanned(). Эта процедура считает количество выполненных процентов, а затем проверяет файл, занося в статистику информацию о нем. Вся статистика по сканированию хранится в статических переменных, открытых для публичного доступа. Из сканера можно узнать, сколько файлов он просканировал, сколько файлов из них не удалось открыть, сколько являлось файлами PE, сколько заражены.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
public class ScanSystem {
    public static int numberofFile = 0;
    public static int numberofPEFile = 0;
    public static int numberofNotOpenedFile = 0;
    public static int numberofVirusFile = 0;
    public static ArrayList<File> virusFiles = new ArrayList<File>();
    public static VirusLibrary virusLibrary=new VirusLibrary("C:\\1\\output.txt");
    private static int i=0;
    public static int procent=0;
    public static void scan(String s) {
        numberofFile = 0;
        numberofVirusFile = 0;
        numberofNotOpenedFile = 0;
        numberofPEFile = 0;
        virusFiles.clear();
        i=0;

        run1(new File(s));
        run(new File(s));

    }

    private static void scaning(File f) {

        i++;
        if(numberofFile!=0)
        procent=(i*100)/numberofFile;
        Main.progressBar.setValue(ScanSystem.procent);

        Main.label.setText(f.toString());
        try {
            Parser p = new Parser(f);
            if(p.doIt()!=1){
            if (virusLibrary.check(p)){
              virusFiles.add(f);
              numberofVirusFile++;
            }
            numberofPEFile++;
            }
        } catch (FileNotFoundException e) {
            numberofNotOpenedFile++;

        } catch (IOException e) {
            numberofNotOpenedFile++;
        }
    }

    public static void run(File file) {
        if (!file.isDirectory()) {
            //numberofFile++;
            scaning(file);
        } else {
            if (file.isDirectory()) {
                String[] temp = file.list();
                if(temp!=null)
                if(temp.length!=0)
                for (String s : temp) {
                    //System.out.println(s);
                    run(new File(file+"\\"+s));
                }
            }
        }
    }
    public static void run1(File file) {
       // System.out.println(file);
        if (!file.isDirectory()) {
            numberofFile++;
            //scaning(file);
        } else {
            if (file.isDirectory()) {
                String[] temp = file.list();
                if(temp!=null)
                if(temp.length!=0)
                for (String s : temp) {

                    run1(new File(file + "\\" + s));
                }
            }
        }
    }
}
База данных

В качестве хранилища базы данных было решено сделать обычный текстовый файл. Данное решение было принято в связи с отсутствием большого количества сигнатур и простоты взаимодействия. В качестве инструмента добавления новых сигнатур вирусов можно использовать непосредственно сам блокнот. Формат базы данных:

  • Количество вирусных сигнатур
  • Сигнатуры

Каждая сигнатура задается именем, размером последовательности байт, и самой последовательностью. Для библиотеки уже выше был рассказан процесс получения тестовой сигнатуры, которую я занес в базу данных. Для взаимодействия с базой данных я создал специальный класс VirusLibrary. В ней есть статический метод check , который проверяет сегмент код на соответствия сигнатурам, находящимся в базе. Также у библиотеки есть метод обновления библиотеки, который перечитывает информацию из текстового файла, остальные методы все скрыты, для того чтобы, в дальнейшем вместо текстового файла использовать уже нормальную базу данных. Также в этом классе я решил поместить важный метод поиска строки в подстроке для двух байтовых массивов.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
import java.io.File;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.Scanner;
public class VirusLibrary {
    public VirusLibrary(String pathToLibrary) {
        this(new File(pathToLibrary));
    }
    public VirusLibrary(File file) {
        mainFile = file;
    }
    File mainFile;
    boolean ready = false;
    private int numberofSegment;
    SegmentCode[] allVirus;
    public void LoadLibrary() throws FileNotFoundException {
        Scanner in = new Scanner(mainFile);
        numberofSegment = in.nextInt();
        allVirus = new SegmentCode[numberofSegment];
        for (int j = 0; j < numberofSegment; j++) {
            String name = in.next();
            int len = in.nextInt();
            byte[] mas = new byte[len];
            for (int i = 0; i < len; i++) {
                mas[i] = (byte) (in.nextInt()-128);
            }
            allVirus[j] = new SegmentCode(mas, name);
        }
        ready = true;
    }

    public void updateLibrary() {
        ready = false;
        try {
            this.LoadLibrary();
        } catch (FileNotFoundException e) {
            e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
        }
    }

    static public boolean KMP(byte[] mas1, byte[] mas2) {
        boolean ans = false;
        if (mas1.length > mas2.length) return ans;
        byte[] all = new byte[mas1.length + mas2.length+1];
        int id = 0;
        for (; id < mas1.length; id++)
            all[id] = mas1[id];
        all[id]=0;
        id++;
        for (; id < mas1.length+mas2.length+1; id++)
            all[id] = mas2[id-mas1.length-1];

       /* for(int i=0;i<all.length;i++){
            System.out.print(all[i]+" ");
        }
        System.out.println();*/

        int[] pref = new int[all.length];
        for(int i=0;i<pref.length;i++) pref[i]=0;
        for(int i=1;i<all.length;i++){
            int j=pref[i-1];
            while (j>0&&(all[i]!=all[j] || i==mas1.length|| j==mas1.length))
                j=pref[j-1];
            if(all[i]==all[j] && i!=mas1.length && j!=mas1.length) j++;
            pref[i]=j;
        }
        for(int i=0;i<pref.length;i++)
                   if(pref[i]==mas1.length) ans=true;
        return ans;
    }
    public boolean check(Parser p){
        boolean ans=false;
        if(!ready) updateLibrary();
        ArrayList<SegmentCode> dangerSegments=new ArrayList<SegmentCode>();
        if(p.isDone()) dangerSegments=p.getAllSegments();
        for(SegmentCode sign:allVirus)
            for(SegmentCode dangerSegment:dangerSegments)
                if(dangerSegment.find(sign)) ans=true;
        return ans;
    }
}
Разбор структуры PE файла

Весь PE файл можно разбить на следующие части: DOS заголовок, DSTUB- заглушка, PEHeader, Optional Header, Data Directory, Section Table. Т.к. наша цель не считать всю информацию о PE файле, зачастую избыточную, а как можно быстрее найти все сегменты кода, то вся задача сводиться к нахождению нужных смещений, но первым делом необходимо проверить является ли файл, который мы сейчас рассматриваем PE файлом. Для проверки файла, сначала проверяю, является ли бинарный файл DOS совместимым. Для этого проверяю первых два байта на равенство сигнатуре MZ, затем перехожу по адресу 0х3с, в котором, если это PE файл должно содержаться смещение до PEHeader. Теперь смещаюсь по этому значению и рассматриваю первых два байта на равенство сигнатуре PE. Теперь эти два байта равны этой сигнатуре, то перед нами PE файл. Зная структуру PE файла, смещаюсь на нужное количество байт и считываю количество секций, теперь перемещаюсь на начало таблицы секций, где содержится вся информация по секциям, из этой информации нас интересует только название секции, ее размер, смещение до ее начала и ее атрибуты. Если секция содержит атрибут секции кода, то параметры этой секции сохраняются. При реализации данного алгоритма на языке программирования Java я столкнулся с проблемой отсутствия беззнаковых типов данных в Java. Я реализовал свой класс для работы с беззнаковыми типами данных.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
import java.io.IOException;
import java.io.RandomAccessFile;
//класс для работы с беззнаковым типом
public class UnsignedTypes {
 private RandomAccessFile nameOfFile;
    //конструктор
    UnsignedTypes(RandomAccessFile f) {
        nameOfFile = f;
    }
    // Статическая функция которая на вход
    // принимает знаковую переменную(любого типа)
    //а на выходе возращает беззнаковый long
    //если ваш компилятор ругается не забудьте
    // превести вашу переменную к типу long явно
    // UnsignedTypes.convert((long)yourvariable)
    static public long convert(long signedvariable){
      long ans=0;
        for(int i=0;i<64;i++){
         ans+=(int)Math.pow(2,i)*(Math.abs(signedvariable % 2));
            signedvariable>>=1;
        }
        return ans;
    }
    //функция чтения WORD(unsigned short)
    public long readWord() throws IOException {
       byte[] mas = new byte[2];
        nameOfFile.readFully(mas,0,2);
        return read(mas,2);
    }
    //функция чтения DWORD(unsigned int)
    public long readDWord() throws IOException {
        byte[] mas = new byte[4];
        nameOfFile.readFully(mas,0,4);
        return read(mas, 4);
    }
    //функция используеая для работы двух верхних функций
    private static long read(byte mas[],int numberOfByte) {
        long ans = 0;
        for (int i = 0; i <numberOfByte; i++) {
            for (int j = 0; j < 8; j++) {
                //System.out.print(Math.abs(mas[i] % 2));
                ans+=(int)Math.pow(2,j+i*8)*(Math.abs(mas[i] % 2));
                mas[i] >>= 1;
            }
            //System.out.print(" ");
        }
        return ans;
    }
    //функция чтения 1байтового Char из бинарного файла
    public char readChar() throws IOException {
        char ans =0;
        byte b=nameOfFile.readByte();
        ans=(char) b;
        return ans;
    }
}

Для удобства работы с бинарным файлом я использовал RandomAccessFile, с помощью которого осуществлялся прямой доступ к файлу и смещение указателя по бинарному файлу. Класс для работы с бинарным файлом я назвал Parser.В нем присутствует публичный метод doIt(), который реализует разбор бинарного файла, и выдает информацию о том является ли файл PE, и был ли он уже разобран до этого. Вторая открытая функция у данного класса это возращение всех сегментов кода из файла. Данный класс является основной единицей взаимодействия в программе, вместе с классом базы вирусов они составляют ядро данного сканера. Вся информация по структуре PE файла я представил в виде одной таблицы в приложениях.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.ArrayList;
//Класс в котором из файла выдираются области кода
public class Parser {
    //сработал ли парсер
    private boolean Done = false;
    // обертка для нашего бинарного файла,которая обеспечивает прямой доступ
    private RandomAccessFile nameOfFile;
    //класс для работы с областями
    Parser(File f) throws FileNotFoundException {
        nameOfFile = new RandomAccessFile(f, "r");
    }
    //количество сегментов
    private long numberOfSegments;
    //попытка парсинга
    public int doIt() throws IOException {
        int ans = 0;
        UnsignedTypes t = new UnsignedTypes(nameOfFile);
        if (this.Done) return 2;
        //реализация парсинга
        char ch1, ch2;
        ch1 = t.readChar();
        ch2 = t.readChar();
        if(!(ch1=='M' && ch2=='Z')) return 1; //если не дос заголовок,то это не ПЕ файл
        nameOfFile.seek(0x3c);                //Если это ПЕ файл то по этому адресу должно лежать смещение
        long offsetTOPEFile = t.readDWord();  //считываем смещение
        nameOfFile.seek(offsetTOPEFile);      //перемещаемся к заголовку ПЕ файла
        ch1 = t.readChar();
        ch2 = t.readChar();
        if(!(ch1=='P' && ch2=='E')) return 1;//последняя поверка на ПЕ
        nameOfFile.seek(offsetTOPEFile+6);
        numberOfSegments= t.readWord();
        nameOfFile.seek(offsetTOPEFile+248);
        for(int i=0;i<numberOfSegments;i++)
            readSegment();
        Done=true;
        return ans;
    }
    private ArrayList<SegmentCode> allSegments=new ArrayList<SegmentCode>();
    public ArrayList<SegmentCode> getAllSegments(){
        return allSegments;
    }
    private void readSegment() throws IOException {
        UnsignedTypes t= new UnsignedTypes(nameOfFile);
        String name="";
        char ch;
        for(int i=0;i<8;i++){
            ch=t.readChar();
            if(ch>21 && ch<128)
                name=name+ch;
        }
        t.readDWord();//DWORD VirtualSize;
        t.readDWord();//VirtualAdress;
        long length=t.readDWord();//   DWORD SizeOfRawData;
        long pointer=t.readDWord();    // DWORD PointerToRawData;
         t.readDWord(); //DWORD PointerToRelocations;
        t.readDWord();//DWORD PointerToLinenumbers;
        t.readWord();//WORD NumberOfRelocations;
        t.readWord();//WORD NumberOfLinenumbers;
        long x=t.readDWord();//DWORD Characteristics;
        ArrayList<Long> temp=new ArrayList<Long>();
        while(x>0){
                temp.add(x%16);
                x=x/16;
            }
        //System.out.println(name);
        if(temp.size()<8 || temp.get(1)!=2) return;
        allSegments.add(new SegmentCode(length,pointer,nameOfFile, name));
    }
    public boolean  isDone(){
       return Done;
    }

}
Поиск вхождений сигнатуры в сегменты данных

Так как при работе антивируса достаточно важна скорость проверки, то для поиска вхождений подпоследовательности в последовательность байт неразумно было использовать обычный поиск за квадратную асимптотику, поэтому для этой цели я использовал алгоритм Кнута-Морисса-Пратта.

Написание пользовательского интерфейса

Для реализации интерфейса к антивирусному сканеру я решил придерживаться концепции минимизации пользовательского интерфейса. Так что весь интерфейс состоит из фрейма, с размещенными на нем тремя кнопками, двумя полями для ввода и отображения текста, баром и меткой. В java присутствует концепция создания интерфейсов, которые сами изменяют свой размер на основе некоторых правил. Это используется для поддержания большей платформенной независимости, но я решил отказаться от этого, потому что моя программа не подходит под этот критерий, так как работает только с PE файлами. В связи с этим я создаю окно 600 на 230, с отступом на 200 по высоте и ширине от начала экрана. Такое окно спокойно поместиться на любом экране, и элементы интерфейса не будут выглядеть маленькими или не помещаться на форме. Все окно я разбиваю на 4 одинаковые полосы по 50пикселей, а30 пикселей идут на обрамление фрейма. В первой полосе находиться кнопка «Выбрать файл библиотеки», а рядом текстовое поле, которое отображает текущий адрес базы данных. При нажатии на кнопку откроется диалоговое окно, в котором вы можете выбрать только файл. На следующей строке расположены кнопка «Выбрать файл или папку для сканирования» и текстовое поле. При нажатии на кнопку появиться диалоговое окно, в котором можно выбрать файл или папку для сканирования, выбор отобразиться в текстовом поле. На следующей строке расположены кнопка «Сканировать» и бар. При нажатии на кнопку сканировать произойдет запуск нового потока, в котором будет осуществляться сканирования файла или папки, а бар будет отображать ход выполнения процесса в процентах. И на последней строке находиться метка, которая выводит информацию о ходе сканирования. Во время процесса сканирования она отображает файл, который подвергается сканированию, а при окончании сканирования в ней отображаются результаты.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class Main {
    public static JTextField textField1;
    public static JTextField textField;
    public static JProgressBar progressBar;
    public static JLabel label;
    public static JFrame frame;
    public static void main(String[] args) {
        frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setBounds(200, 200, 600, 230);
        frame.setLayout(null);
        frame.setName("Антивирусный сканер");
        JButton button = new JButton("Выбрать файл библиотеки");
        button.setBounds(0, 0, 300, 50);
        JButton button1 = new JButton("Выбрать файл или папку для сканирования");
        button1.setBounds(0, 50, 300, 50);
        JButton button2 = new JButton("Сканировать");
        button2.setBounds(0, 100, 300, 50);
        frame.add(button);
        frame.add(button1);
        frame.add(button2);
        progressBar = new JProgressBar(0, 100);
        progressBar.setBounds(300, 100, 300, 50);
        progressBar.setBorderPainted(true);
        progressBar.setBackground(Color.white);
        progressBar.setValue(0);
        frame.add(progressBar);
        textField = new JTextField();
        textField.setBounds(300, 0, 300, 50);
        textField1 = new JTextField();
        textField1.setBounds(300, 50, 300, 50);
        textField.setText("C:\\1\\output.txt");
        textField.setEditable(false);
        textField1.setText("C:\\Program");
        textField1.setEditable(false);
        frame.add(textField);
        frame.add(textField1);
        progressBar.setStringPainted(true);
        label = new JLabel("Антивирус готов к работе");
        label.setBounds(0, 150, 600, 50);
        frame.add(label);
        frame.setVisible(true);
        button2.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                new Thread(new Runnable() {
                    public void run() {
                        ScanSystem.scan(textField1.getText());
                        Main.label.setText("Всего: " + ScanSystem.numberofFile + " Исполняемых: " + ScanSystem.numberofPEFile + " Неоткрывшихся: " + ScanSystem.numberofNotOpenedFile + " Зараженных: " + ScanSystem.numberofVirusFile);

                    }
                }).start();


            }
        });
        button1.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                JFileChooser fileChooser = new JFileChooser();
                fileChooser.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
                fileChooser.showOpenDialog(frame);
                textField1.setText(fileChooser.getSelectedFile().toString());
            }
        });
        button.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                JFileChooser fileChooser = new JFileChooser();
                fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
                fileChooser.showOpenDialog(frame);
                textField.setText(fileChooser.getSelectedFile().toString());
            }
        });
    }
}
Тестирование антивирусного сканера

Для тестирования данной программы следует проверить качество обнаружения сигнатур вируса и скорость его работы, также при тестировании могут выявиться некоторые ошибки, которые не были устранены при разработке антивирусного сканера. Для тестирования качества обнаружения создадим десять различных файлов, которые будут содержать код вируса, и десять таких же файлов без вируса. Для этого будем располагать сигнатуру следующими образами. Возьмем нормальный код, и поместим сигнатуру в write() в начало файла, а sleep() в середину, затем поместим sleep() в центр и в начало, таким образом, возьмем все возможные перестановки. При всех возможных перестановках выяснилось, что объект все равно определяется как вирус, но если в программе не присутствует либо sleep либо write, то данная программа уже не определяется как вирус, что доказывает корректную работу антивируса и правильность сделанной накануне сигнатуры. Теперь следует провести тест на скорость, для этого папку в которой содержатся файлы, которые я тестировал по отдельности, просканируем полностью. Время сканирования вышло около 1 секунды, увеличивая количество файлов. Увеличивая количество файлов до 780,из них исполняемых до 88, время работы антивируса осталось неизменным. Увеличив размер папки до 5 гигабайт, количество файлов до 15 тысяч, а количество исполняемых до 1500 тысячи файлов антивирус проработал 10 секунд, найдя 561 вирус. Для интереса я попробовал просканировать папку Windows, антивирусный сканер просмотрел 30 процентов, а на одном файле у него не хватила места, чтобы создать массив байт, поэтому моя программа упала с ошибкой. Я увеличил размер максимальной кучи для jvm до 500 мегабайт. Это решило проблему. Для сканировании папки Windows антивирусному сканеру понадобилось около 2 минут. Он нашел 14 тысяч файлов, из них исполняемых половина. После данного теста я решил пойти дальше и попробовать просканировать весь свой компьютер для тестирования проблемы с размером кучи в java.Сканирования всего компьютера заняло 10 минут, было найдено 88 тысяч файлов, 13 тысяч исполняемых, из них 561 зараженных.Данные тестирования показывают, что при небольших объемах данных или при отсутствии ограничений по времени антивирусный сканер выполнил свою задачу. Понятно, что в базе данных должно быть больше чем одна сигнатура, что может значительно уменьшить скорость работы сканера, потому что сейчас основное время тратиться не на анализ сигнатуры, а на разбор PE файла. Для устранения данных недостатков планируется введение дополнительных параметров в сигнатуру.
антивирусный сканер

Заключение

Реализовав простой сканнер и проверив его на тестовых файлах, стоит отметить, не смотря, на использование алгоритма Кнута-Морисса-Пратта достаточно медленное время работы. Для увеличения скорости работы сканнера в дальнейшем планируется введение дополнительных параметров для определения сигнатур, таких как хеш-суммы, сrc- суммы возможные смещения и т.п. Программа, несмотря на возможность проверки PE файлов, не умеет работать с архивами и сжатыми данными. Планируется ввести распаковку стандартных архивов, таких как zip и rar. Кроме PE файлов заражению могут подвергаться и другие файлы, работа которых связанна в основном со скриптовыми языками. Возможность нахождения скрипт вирусов также важна для дальнейшего развития данной тематики. Но на данный момент реализован антивирусный сканнер, который выполнил задачи, которые стояли перед ним

  • мария

    Очень интересно ,спасибо зща материал

  • Ilya Vasiljev

    Прикольно. Надо ща все это замутить, а потом на c# перевести)))

  • Ilya Vasiljev

    Прикольно. Надо ща все это замутить, а потом на c# перевести)))

  • Артем

    А можно пример файла с сигнатурой?

Научиться программировать

  • на Delphi

  • на Java

  • на C++