Статьи «Что нам стоит антивирь построить?!» (1 часть)

Discussion in 'Статьи' started by Spider Agent, 18 Feb 2009.

  1. Spider Agent

    Spider Agent Elder - Старейшина

    Joined:
    22 Feb 2007
    Messages:
    6
    Likes Received:
    19
    Reputations:
    0
    Такой штукой как антивирус сейчас никого не удивить. Каждый пользователь считает своим долгом иметь у себя на компьютере такую зверушку. И готов спорить пока вы читаете данные строки вашу систему постоянно мониторит какой-нибудь Касперский, а быть может Nod32… Но вы никогда не задумывались как работают программы такого рода ? И не появлялось ли желание собрать такую «штуку» самому? Если да, эта статья именно для вас. В ней я рассмотрел методику создания простейшего, повторяюсь простейшего антивирусного сканера(но вполне рабочего) способного обнаружить и уничтожить львиную долю вредоносного по.

    Немного теории

    Не буду заниматься разного рода «болтологией» на тему что такое вирус, какие они бывают, и что случается, если они попадают на компьютер. Не маленькие, должны уже сами все это знать:) А если не знаете, то спросите Google или Yandex. Наш сканер будет искать вирусы, которые не умеют заражать другие файлы, а под эту категорию попадают: черви, трояны, логические бомбы, кейлогеры. Вы спросите почему? Дело в том, что для обнаружения файлового вируса, в первую очередь необходимо нехилое знание assembler’a, для того, что бы узнать алгоритм работы вредоносной программы. Как мы будем бороться с вирусом, если не знаем, как он устроен? Во-вторых, нужно знание структуры PE-файла. А это дело непростое, особенно на языках высокого уровня, таких как Delphi.(в нашем случаи мы обойдемся простым расчетом контрольной суммы) И, в-третьих, мы же являемся начинающими. Сразу попытаться взять «быка за рога» у нас не получиться. Я не хочу сказать, что это невозможно, просто надо всегда с чего-то начинать, а, как правило, начинают с простейшего. Теперь мы разобрались, какой антивирус будем делать, и пора бы перейти к самому главному – к алгоритму его работы. Как вы знаете у любого антивируса есть своя база, в которой он хранит сигнатуры известных ему вредоносных программ (по сути, это называется контрольной суммой). Так вот это и позволяет ему отличать вирус от обычной программы. Контрольная сумма (crc) – некое число (оно может быть 16-,32-,64-битным, да хоть 512-битным. Все зависит от фантазии разработчика), которое характерно для участка кода. То есть у каждого файла своя контрольная сумма. Не существует универсального способа подсчета crc, на самом деле их очень много, но мы рассмотрим те, которые подходят для наших целей. Наш антивирус умеет считать контрольные суммы. Что еще ему нужно? Правильно, ему нужны файлы crc которых он будет сверять с сигнатурами из базы. А чтобы найти файлы для сканера существуют очень удобные api функции, о которых ты наверно не раз слышал: FindFirst, FindNext, FindClose. Они позволят нам устроить глобальный поиск по всему жесткому диску. Останется только навешать разного рода украшений, таких как окошко, сигнализирующее о том, что мы нашли вирус, противный звук, предназначенный все для того же, статистику и отчет о сканировании, и «домашний» антивирус готов.

    От слов к делу: CRC

    В нашем случаи выбор алгоритма расчета контрольной суммы особой роли не сыграет. Если конечно мы не собираемся делать коммерческий продукт, ведь тогда придется разрабатывать свой вариант подсчета crc(мы же не хотим отваливать проценты автору алгоритма) Если все же нашлись желающие попробовать написать платный антивирус, то советую почитать «Элементарное руководство по CRC-алгоритмам обнаружения ошибок» автор:Ross N. Williams. Причем наш вариант должен быть очень надежным. Но т.к. в этой статье просто рассматриваются основные методики создания простейшего антивирусного сканера, то мы не будем изобретать велосипед и воспользуемся уже готовым разработками. К счастью таких в Интернете очень много. Следующая функция является простой и быстрой реализации контрольной суммы:
    Code:
    function FastCheckSum(FileName: string): DWORD;
    
    var
    
    F: file of DWORD;
    
    P: Pointer;
    
    Fsize: DWORD;
    
    Buffer: array[0..500] of DWORD; // можно конечно и побольше взять
    
    begin
    
    FileMode := 0;
    
    AssignFile(F, FileName);
    
    // ассоциируем файловую переменную с файлом, с которым будем работать
    
    Reset(F);
    
    // открываем файл для чтения/записи
    
    Seek(F, FileSize(F) div 2);
    
    // ставим положение считывания/записи на FileSize(F) div 2 байт – в середину
    
    //файла
    
    Fsize := FileSize(F) - 1 - FilePos(F);
    
    // FilePos(F) определяет текущую позицию в файле 
    
    if Fsize > 500 then
    
    Fsize := 500;
    
    // Если решите изменить размер массива Buffer, то и здесь тоже нужно менять
    
    BlockRead(F, Buffer, Fsize);
    
    // считываем из файла Fsize байт
    
    Close(F);
    
    //закрываем наш файл
    
    P := @Buffer;
    
    asm
    
    xor eax, eax // обнуляем eax
    
    xor ecx, ecx // обнуляем ecx
    
    mov edi , p // в регистр edi помещаем значение переменной p
    
    @again: //наша метка
    
    add eax, [edi + 4*ecx] 
    
    inc ecx 
    
    // увеличиваем значение регистра ecx на 1 - своеобразный счетчик
    
    cmp ecx, fsize // сравниваем и если не равны то
    
    jl @again //перейдем обратно – к метке 
    
    mov @result, eax 
    
    // а если равны, то результату работы функции пваиваем значение регистра eax
    
    end;
    
    end;
    Лично мне это вариант очень нравится – он достаточно несложный, но при этом во время его тестирования серьезных багов замечено не было. Плюс ко всему цикл реализован при помощи asm вставок, что позволяет увеличить скорость работы (конечно, это можно было реализовать на Delphi, но нам важна скорость). Вообще особых вопросов по коду возникнуть не должно. Но все же для полного понимания я закомментировал некоторые участки. Для людей, которые сомневаются в данном алгоритме я припас юниты (находится в архиве с исходниками), способные рассчитать crc32 и crc64. Разумеется с примерами. А для любителей «самопала» могу предоставить следующий код, написанный буквально за минуту:

    Code:
    function sampleCRC(filename: string): dword;
    
    var
    
    File_: hFile;
    
    ReOpenBuff: OFSTRUCT;
    
    buffer: array[0..1024] of byte;
    
    counter, summa, read: cardinal;
    
    begin
    
    summa := 0;
    
    File_ := OpenFile(PChar(filename), ReOpenBuff, OF_READ); 
    
    // Открываем файл для чтения
    
    ReadFile(File_, buffer, 1024, read, nil);
    
    // Читаем наш файл
    
    CloseHandle(File_);
    
    // Закрываем его
    
    for counter := 0 to 1024 do
    
    begin
    
    summa := summa + buffer[counter];
    
    // побайтно складываем – не слишком оригинально ?
    
    end;
    
    result := summa;
    
    end;
    Конечно, данный алгоритм надежным назвать сложно, но его с легкость можно усовершенствовать. К примеру, можно увеличить количество считываемых байт, а еще лучше создать динамический массив, тем самым станет возможным считывать контрольную сумму всего файла (хотя контрольной суммой я бы это не назвал с трудом). Идей на самом деле много, но, во-первых, надо и вам дать подумать, а во-вторых, статья не резиновая ?

    Антивирусная база

    Антивирусная база – это сердце любого антивируса. Благодаря ней он спасает нас злых ][акеров которые так и норовят угнать наши пароли от аськи, мыла, . Я долго думал как же «прикрутить» к нашему антивирусу базу. Хранить сигнатуры и имена вирусов в самом Exe’шнике? Нет, это уж слишком примитивно! Надо что бы пользователь при желании мог ее обновить. В общем, немного подумав, я пришел к такому выводу, что проще всего все это дело хранить в текстовом файле.Да-да, именно в текстовом файле. И сейчас вы поймете почему. Идея состоит в следующем: у нас есть два текстовых файла – в одном хранятся сигнатуры, а в другом имена вирусов.

    [​IMG]
    Файлы антивирусной базы. В vb.vdb хранятся сигнатуры, а в vn.vdb имена вирусов

    Несложно догадаться, по какому принципу они там расположены.(См. скриншот) Первой сигнатуре соответствует первое имя, второй второе и.т.д. Когда мы будем писать процедуру сканирования вы поймете, зачем я сделал такой выбор, а пока сделаем простенькую утилиту, при помощи которой будем добавлять новые вирусы в базу. Для начала кинем на форму два компонента Edit , два компонента Button, и один Open Dialog. В обработчике нажатия кнопки, которая будет добавлять сигнатуру в базу, напишем следующий код:

    Code:
    procedure TForm1.AddToBaseClick(Sender: TObject);
    
    var
    
    sum: Dword;
    
    bas: TextFile;
    
    VirusName: textFile;
    
    begin
    
    Sum := FastCheckSum(Edit1.Text); \\ читаем контрольную сумму
    
    AssignFile(bas, 'Base\vb.bas'); \\ ассоциируемся с файлом базы(сигнатур)
    
    Append(bas); \\ открываем для записи в конец
    
    Writeln(bas, sum); \\ собственно пишем
    
    closeFile(bas); \\закрываем файл
    
    AssignFile(VirusName, 'base\vn.bas');
    
    Append(VirusName); // тут
    
    Writeln(VirusName, Edit2.text); // все
    
    closefile(VirusName); //аналогично
    
    end;
    Как видите, ничего в этом сложного нет. Мы только что написали собственную утилиту, добавляющую новые записи в нашу антивирусную базу. Для большей конспирации мои файлы имеют расширение *.bas(Возможно такой ход не понравиться любителям Бэйсика). Наверно вас смущает «прозрачность» файлов т.к. их можно просмотреть блокнотом. Ничего страшного, кто вам мешает все это дело защитить простеньким xor-шифрованием. Сразу хочу заметить, что мы могли бы работать с типизированными файлами. Например, создать свою структуру наподобие:

    Code:
    type
    TMyBase = record
    VirusName : string[30];
    Signature : LongInt;
    end;
    
    
    … // и добавлять вирус в базу уже так:
    
    Procedure AddToBase(VirName:String; VirSignature:integer); 
    
    var
    
    table: TMyBase;
    
    AntiVirBase: file of TMyBase;
    
    begin
    
    AssignFile(AntiVirBase, ‘base.bas’); //ассоциируем, ну уже должны знаете 
    
    Reset(AntiVirBase); // открываем файл для чтения/записи
    
    table.VirusName := VirName; // получаем значениями
    
    table.Signature := VirSignature; // которые будем записывать в базу 
    
    Seek(AntiVirBase, FileSize(AntiVirBase)); //переходим в конец
    
    write(AntiVirBase, table); // и только теперь пишем
    
    closefile(AntiVirBase); //ну конечно закрываем нашу базу
    
    end;
    Теперь нашу базу открыть блокнотом не получится. В смысле открыть то можно, но ничего не поймем. Вам осталось решить какой из приведенных вариантов выбрать, лично я предпочел первый из-за его простоты, хотя вы можете со мной не согласиться и использовать второй (дальше я буду рассматривать только первый случай, но если вы чуть-чуть напряжете свои мозги, то сможете адаптировать наш антивирус под оба способа).

    Теперь разберемся с OpenDialog. Кто знает для чего он нужен, могут пропустить этот абзац, а кто нет - читайте. Значит так, по нажатию второй кнопки должно вылезти диалоговое окно с выбором файла – зловредного вируса. Конечно, в Edit можно вводить вручную, но это так нудно. В обработчике нажатия кнопки наберите следующий код:

    Code:
    procedure TForm1.Button2Click(Sender: TObject);
    
    begin
    
    if OpenDialog1.Execute then // в общем тут комментировать нечего ?
    
    begin
    
    Edit1.Text := OpenDialog1.FileName;
    
    end;
    
    end;
    В принципе мы можем этим ограничится, а можем чуть-чуть улучшить наше диалоговое окно. Например, установить фильтры только на выбор *.exe и *.dll файлов:

    Code:
    procedure TForm1.FormCreate(Sender: TObject);
    
    begin
    
    with OpenDialog1 do
    
    begin
    
    OpenDialog1.Filter := ‘Exe файлы (*.exe)|*.exe|Dll файлы (*.dll)|*.dll’;
    
    // ставим фильтры
    
    end;
    Хмм.… А вы не задумались, почему мы фильтры ставим только на такие файлы. Ведь есть черви, написанный на скриптовых языках (таких как VBScript или JScript). Что нам мешает добавить сигнатуру этих вирусов? Ответ как всегда прост. Достаточно добавить в скрипт пустую строчку и crc уже будет другой. Тут надо анализировать сам текст скрипта, но это уже совсем другая история…

    Neon,
    [email protected]
    Источник: SASecurity IB
     
    #1 Spider Agent, 18 Feb 2009
    Last edited by a moderator: 18 Jun 2010
  2. Spider Agent

    Spider Agent Elder - Старейшина

    Joined:
    22 Feb 2007
    Messages:
    6
    Likes Received:
    19
    Reputations:
    0
    Упс, продублировалась. Моды иль адм - удалите, плз.
     
  3. !Shoorf

    !Shoorf New Member

    Joined:
    17 Jun 2010
    Messages:
    0
    Likes Received:
    0
    Reputations:
    0
    Огромное спасибо автору за статью! Господа, а кто-нибудь пробовал использовать какие-либо части приведенного здесь кода в проекте? У меня например первая функция расчета контрольной суммы не захотела работать, либо сваливается с acces violation, либо ещё с чем. Думал сначала что натравливаю её на открытый файл, но после тестирования понял, что ошибка возникает всегда.