Logowanie
Nazwa:
Hasło:
Statystyki
Ilość postów: 6382
Użytkowników: 11275
Gości: 23

Ostatnio dołączył:
waleunrensuff

Najwięcej postów:
kristoff

Online:
brak zalogowanych użytkowników
Artykuły
C/C++ Tworzymy własny skaner antywirusowy
W artykule omówimy poszczególne etapy tworzenia prostego skanera antywirusowego.
Zadaniem artykułu jest wyjaśnienie podstawowych zasad pracy programów antywirusowych, wykorzystujących sygnatury dla rozpoznania szkodliwego kodu.
Oprócz skanera napiszemy również program do tworzenia bazy sygnatur.

Ze względu na prostotę algorytmu sprawdzania, nasz skaner może wykrywać tylko szkodliwe programy, które rozprzestrzeniają się całymi plikami, tzn. takie, które nie zarażają innych plików i nie zmieniają swoich właściwości podczas działania, jak wirusy polimorficzne.
Poza tym, odnosi się to do większości wirusów, robaków i praktycznie wszystkich trojanów, dlatego stworzony przez nas skaner okaże się niezwykle pożytecznym narzędziem.




Sygnatura to nic innego jak unikalna część pliku (bajt).
Jednak ta część powinna maksymalnie jednoznacznie wyróżniać plik spośród mnóstwa innych plików.
Oznacza to, że wybrana sekwencja powinna odpowiadać tylko jednemu (i żadnemu innemu) plikowi, do którego należy.

W praktyce oprócz samej sekwencji stosowane są także dodatkowe parametry, pozwalające maksymalnie jednoznacznie zestawić sygnaturę z plikiem.
Wprowadzenie dodatkowych parametrów jest także skierowane na przyspieszenie wyszukiwania sygnatury w pliku.
Takimi parametrami może być np. rozmiar pliku, zastąpienie sekwencji bajtów, typ pliku, specjalne maski (wyobrażenie jak powinien wyglądać przykładowy plik, aby mogła się w nim zawierać wymagana sygnatura) i wiele innych.

W naszym skanerze, w charakterze dodatkowego parametru, będziemy wykorzystywać przeniesie sekwencji w pliku, w odniesieniu do początku.
Ta uniwersalna metoda znajduje zastosowanie dla dowolnych plików niezależnie od typu.
Jednak wykorzystywanie przeniesienia sekwencji posiada jeden znaczący minus: aby "oszukać" skaner, wystarczy delikatnie przesunąć sekwencję bajtów w pliku, czyli zmienić przeniesienie sekwencji (np. ponownie kompilować wirus lub dodać symbol w przypadku skryptowego wirusa).

Dla zaoszczędzenia pamięci i podwyższenia szybkości wykrywania, w praktyce zwykle wykorzystuje się liczbę kontrolną (hash) sekwencji.
W ten sposób przed dodaniem sygnatury do bazy określana jest liczba kontrolna wybranego odcinka pliku. Pomaga to także w niewykrywaniu szkodliwego kodu we własnych bazach.




Baza antywirusowa to jeden lub kilka plików, posiadających zapis o wszystkich wirusach znanych skanerowi.
Każdy zapis obowiązkowo zawiera nazwę wirusa (w końcu użytkownik powinien wiedzieć, co za intruz pojawił się w jego systemie). W zapisie powinna występować też sygnatura, poprzez którą wykonywane jest wyszukiwanie.
Oprócz nazwy i sygnatury, zapis może zawierać dodatkowe informacje, np. instrukcję dotyczącą usunięcia skutków pojawienia się wirusa.




Algorytm pracy skanera, wykorzystującego sygnatury, można sprowadzić do kilku punktów:
1. Pobranie bazy sygnatur
2. Otworzenie sprawdzanego pliku
3. Wyszukiwanie sygnatury w otworzonym pliku
4. Po odnalezieniu sygnatury
- podjęcie stosownych działań
5. Jeśli żadna z bazowych sygnatur nie zostanie odnaleziona
- zamknięcie pliku i przejście do sprawdzenia następnego.

Jak widać, ogólna zasady działania skanera jest całkiem prosta.

Teorii już wystarczy. Przejdźmy do części praktycznej.



Zanim zaczniemy pisać kod, warto ustalić wszystkie części składowe skanera i to, w jaki sposób będą ze sobą współpracować.

W celu wykrycia szkodliwych plików niezbędny jest nam sam skaner.
Dla działania skanera niezbędne są sygnatury, które chronione są w bazie antywirusowej.
Specjalny program tworzy i uzupełnia bazę.
W rezultacie zawiązuje się następująca zależność:

Program tworzący bazę -> Baza -> Skaner

Jednak zanim stworzymy bazę, konieczne jest ustalenie jej formatu, który zależy z kolei od przechowywanych informacji.

Informacja sygnatury

Sygnatura będzie się składać z:
- przesunięcia (modyfikacji) sekwencji w pliku
- rozmiaru sekwencji
- hasha sekwencji

W celu "hashowania" będziemy posługiwać się algorytmem MD5.
Każdy MD5-hash składa się z 16 bajtów lub 4 podwójnych słów.
Aby zachować zmiany i rozmiar sekwencji zarezerwujemy po 4 bajty.

W ten sposób sygnaturę możemy opisać w nastepujący sposób:

[Offset * 4 ]
[Lenght * 4 ]
[Hash * 16 ]


Zapis bazy antywirusowej

Zapis będzie zawierał:
- Sygnaturę
- Rozmiar nazwy pliku
- Nazwę pliku

Dla rozmiaru nazwy pliku zostawimy 1 bajt, co w zupełności wystarczy.
Nazwa pliku może mieć dowolny rozmiar, nie przekraczający 255 znaków.

Otrzymujemy następującą strukturę:

[Signature]
[NameLen * 1 ]
[Name ... ]

Po odkryciu struktury sygnatury otrzymujemy zapis:

[Offset * 4 ]
[Lenght * 4 ]
[Hash * 16]
[NameLen * 1 ]
[Name ... ]

Właśnie w ten sposób - w pliku, jeden za drugim, będą znajdować się zapisy dla wszystkich, znanych skanerowi, szkodników.

Oprócz samych zapisów w pliku bazy powinien znajdować się nagłówek, który będzie zawierać liczbę rekordów w bazie i sygnaturę pliku "AVB". Wyznaczenie sygnatury - będzie przekonywać, że jest to właśnie plik bazy.

W ten sposób plik bazy będzie mieć strukturę:

[Sign * 3 ]
[RecordCount * 4 ]
[Records]


Przejdźmy zatem do napisania kodu.






Nie jest ich dużo, bo dwie.
Dane struktury będą wykorzystywane zarówno przez skaner jak i program, który stworzył bazę antywirusową.
Po pierwsze, musimy koniecznie przywołać wszystkie niezbędne nam struktury.


Pierwszą z nich będzie struktura sygnatury SAVSignature.
Kolejną będzie struktura zapisu SAVRecord, obejmująca sygnaturę z nazwą.
Dla wygody struktura będzie zawierać funkcję wydzielenia pamięci na nazwę szkodnika (allocName).

Wszystkie struktury znajdują się w pliku avrecord.h

Listing :
 
#ifndef _AVRECORD_H__INCLUDED_
#define _AVRECORD_H__INCLUDED_
#include <windows.h>
 
 
//! Struktura sygnatury
typedef struct SAVSignature{
        SAVSignature(){
                this->Offset = 0;
                this->Lenght = 0;
                memset(this->Hash, 0, sizeof(this->Hash));
        }
        DWORD   Offset;         // - modyfikacja  pliku
        DWORD   Hash[4];        // - MD5 hash
        DWORD   Lenght;         // - rozmiar danych
} * PSAVSignature;
 
//! Struktura zapisu o szkodniku
typedef struct SAVRecord{
        SAVRecord(){
                this->Name           = NULL;
                this->NameLen        = 0;
        }
        ~SAVRecord(){
                if(this->Name != NULL)  this->Name;
        }
        //! Wyznaczenie pamięci na nazwę
        void    allocName(BYTE NameLen){
                if(this->Name == NULL){
                        this->NameLen        = NameLen;
                        this->Name           = new CHAR[this->NameLen + 1];
                        memset(this->Name, 0, this->NameLen + 1);
                }
        }
        PSTR                    Name;           // - Nazwa
        BYTE                    NameLen;        // - Rozmiar nazwy
        SAVSignature    Signature;      // - Sygnatura
       
} * PSAVRecord;
 
#endif
 





Kolejny etap to napisanie klasy dla pracy z plikiem bazy antywirusowej.
Klas będzie kilka:
- Bazowa klasa pliku "CAVBFile"
- Klasa odczytu pliku "CAVBFileReader"
- Klasa dodawania zapisu "CAVBFileWriter"

Informacje wszystkich klas znajdują się w pliku CAVBFile.h

Przedstawienie klas pracy z plikiem bazy
 
#ifndef _AVBFILE_H__INCLUDED_
#define _AVBFILE_H__INCLUDED_
#include <fstream>
#include <windows.h>
#include "avrecord.h"
using namespace std;
 
 
/*              Format pliku bazy antywirusowej
       
        [AVB]                                   // - Sygnatura
        [RecordCount    * 4 ]   // - Ilość zapisów
        [Records                ... ]
 
        Record:
                [Offset         * 4 ]   // - Modyfikacja
                [Lenght         * 4 ]   // - Rozmiar
                [Hash           * 16 ]  // - Suma kontrolna
                [NameLen        * 1 ]   // - Rozmiar nazwy
                [Name           ... ]   // - Nazwa szkodnika
 
*/

 
 
//! Klasa pliku bazy antywirusowej
typedef class CAVBFile{
protected:
        fstream         hFile;                  // - ?????? ?????? ?????
        DWORD           RecordCount;    // - Ilość zapisów
public:
        CAVBFile();
 
        //! Zamknięcie pliku
        virtual void close();
        //! Sprawdzenie stanu wpisów
        virtual bool is_open();
        //! Otrzymanie ilości zapisów
        virtual DWORD getRecordCount();
} * PCAVBFile;
 
 
//! Klasa dla zapisu pliku
typedef class CAVBFileWriter : public CAVBFile{
public:
        CAVBFileWriter() : CAVBFile(){
        }
 
        //! Otworzenie pliku
        bool    open(PCSTR FileName);
        //! Dodanie zapisu do pliku
        bool    addRecord(PSAVRecord    Record);
 
} * PCAVBFileWriter;
 
//! Klasa dla odczytu pliku
typedef class CAVBFileReader : public CAVBFile{
public:
        CAVBFileReader() : CAVBFile(){
 
        }
        //! Otworzenie pliku
        bool    open(PCSTR FileName);
        //! Odczyt zapisu
        bool    readNextRecord(PSAVRecord Record);
 
} * PCAVBFileReader;
 
 
#endif
 



Przejdźmy teraz do realizacji przedstawionych klas.
Ich realizacja będzie odbywać się w pliku AVBFile.cpp
Pamiętajmy, że musimy dołączyć plik AVBFile.h

W niektórych funkcjach będzie nam potrzebne sprawdzenie czy plik istnieje.

Listing :
 
//! Sprawdzenie występowania pliku
bool    isFileExist(PCSTR FileName){
        return GetFileAttributesA(FileName) != DWORD(-1);
};
 


To najszybszy sposób sprawdzenia czy plik istnieje. Wykorzystywany jest w większości przykładów w MSDN, więc może być traktowany jako standard dla Windows.
Funkcja GetFileAttributes przywraca atrybuty pliku lub 0xfffffff w przypadku, kiedy plik nie zostanie odnaleziony.

Przechodzimy do realizacji funkcji klasy bazowej.

Listing :
 
CAVBFile::CAVBFile(){
        this->RecordCount = 0;
}
//! Zamknięcie pliku
void CAVBFile::close(){
        if(hFile.is_open())     hFile.close();
}
//! Sprawdzenie stanu pliku
bool CAVBFile::is_open(){
        return hFile.is_open();
}
//! Otrzymanie ilości plików
DWORD   CAVBFile::getRecordCount(){
        return this->RecordCount;
}
 


Następnie realizujemy funkcję klasy dla zapisu pliku

Listing :
 
 
//
// - CAVBFileWriter
//
 
//! Otworzenie pliku
bool CAVBFileWriter::open(PCSTR FileName){
        if(FileName == NULL) return false;
        // - Jeśli plik nie zostanie znaleziony, tworzymy jego prototyp
        if(!isFileExist(FileName)){
                hFile.open(FileName, ios::out | ios::binary);
                if(!hFile.is_open()) return false;
                hFile.write("AVB", 3);                                                                        // - Sygnatura pliku
                hFile.write((PCSTR)&this->RecordCount, sizeof(DWORD));   // - Ilość zapisów
        // - Inaczej otwieramy i sprawdzamy ważność
        }else{
                hFile.open(FileName, ios::in | ios::out | ios::binary);
                if(!hFile.is_open()) return false;
                // - Sprawdzenie sygnatury
                CHAR    Sign[3];
                hFile.read((PSTR)Sign, 3);
                if(memcmp(Sign, "AVB", 3)){
                        hFile.close()// - To obcy plik
                        return false;
                }
                // - Odczytujemy ilość zapisów
                hFile.read((PSTR)&this->RecordCount, sizeof(DWORD));
        }
        return true;
}
 
bool CAVBFileWriter::addRecord(PSAVRecord Record){
        if(Record == NULL || !hFile.is_open()) return false;
        // - Przenosimy się na koniec pliku
        hFile.seekp(0, ios::end);
        // - Dodajemy zapis
        hFile.write((PSTR)&Record->Signature.Offset,     sizeof(DWORD))// - Modyfikacja sygnatury
        hFile.write((PSTR)&Record->Signature.Lenght,     sizeof(DWORD))// - Rozmiar sygnatury
        hFile.write((PSTR)&Record->Signature.Hash,  4 *  sizeof(DWORD))// - Suma kontrolna
        hFile.write((PSTR)&Record->NameLen, sizeof(BYTE));       // - Rozmiar nazwy
        hFile.write((PSTR)Record->Name, Record->NameLen)// - Nazwa
        // - Przesuwamy się do ilości zapisów
        hFile.seekp(3, ios::beg);
        // - Zwiększamy licznik zapisów
        this->RecordCount++;
        hFile.write((PSTR)&this->RecordCount, sizeof(DWORD));
 
        return true;
}
 


Przy otworzeniu pliku, jeśli plik nie został znaleziony, tworzy się nowy plik i w nim zapisywana jest sygnatura i liczba wpisów).
Jeśli plik istnieje, przeprowadzane jest sprawdzenie sygnatury pliku i odczyt liczby wpisów.

Funkcja addRecord przyjmuje w charakterze parametru odnośnik do struktury dodanego zapisu.
Najpierw przenosimy się na koniec pliku (nowy wpis jest tam notowany).
Następnie ma miejsce zapis danych w pliku zgodnie z ustalonym formatem.
Po dokonaniu zapisu następuje zwiększenie licznika zapisów.


Listing :
 
//
// - CAVBFileReader
//
bool    CAVBFileReader::open(PCSTR FileName){
        if(FileName == NULL) return false;
        // - Jeśli plik nie został znaleziony, to wykonujemy jego prototyp
        if(isFileExist(FileName)){
                hFile.open(FileName, ios::in | ios::out | ios::binary);
                if(!hFile.is_open()) return false;
                // - Sprawdzenie sygnatury
                CHAR    Sign[3];
                hFile.read((PSTR)Sign, 3);
                if(memcmp(Sign, "AVB", 3)){
                        hFile.close()// - To obcy plik
                        return false;
                }
                // - Odczytujemy liczbę zapisów
                hFile.read((PSTR)&this->RecordCount, sizeof(DWORD));
        }else{ return false; }
        return true;
}
 
bool    CAVBFileReader::readNextRecord(PSAVRecord Record){
        if(Record == NULL || !hFile.is_open()) return false;
 
        hFile.read((PSTR)&Record->Signature.Offset,      sizeof(DWORD))// - Modyfikacja sygnatury
        hFile.read((PSTR)&Record->Signature.Lenght,      sizeof(DWORD))// - Rozmiar sygnatury
        hFile.read((PSTR)&Record->Signature.Hash,  4 *   sizeof(DWORD))// - Suma kontrolna
        hFile.read((PSTR)&Record->NameLen, sizeof(BYTE));        // - Rozmiar nazwy
        Record->allocName(Record->NameLen);
        hFile.read((PSTR)Record->Name, Record->NameLen)// - Nazwa
        return true;
}
 


W tym przypadku, jeśli podczas próby otworzenia pliku, okazuje się że plik nie istnieje, funkcja zwróci wartość false, świadczącą o błędzie.
Odczyt zapisu dokonuje się stopniowo i zabezpieczony jest funkcją readNextRecord, która w charakterze parametru przyjmuje odnośnik do struktury zapisu, w której będą odczytywane dane z pliku.



Na tym skończymy pisanie ogólnego kodu.
Pora przejść do realizacji programu tworzącego wpisy i skanera.





Jak już wcześniej wspomniano, skaner bez sygnatur nie ma sensu. Właśnie dlatego pierwszą rzeczą, którą wykonamy będzie program tworzący bazy.

W charakterze parametrów, program przyjmie ścieżkę do pliku szkodnika, ścieżkę do pliku bazy, zastąpienie sekwencji w pliku szkodnika, rozmiar sekwencji i nazwę szkodnika.
Argumenty przekazywane są w formacie -A[Value], gdzie A to odpowiadający klucz, a Value - wartość.
Oznaczymy wszystkie argumenty:
-s = ścieżka do pliku szkodnika
-d = ścieżka do pliku bazy
-o = modyfikacja sekwencji
-l = rozmiar sekwencji
-n = nazwa pliku

Algorytm pracy programu:
1. Otworzyć plik szkodnika
2. Przejść stosując się do zmian
3. Obliczyć MD5-hash sekwencji bajtów
4. Dodać zapis do bazy

Wykonania algorytmu nie będziemy przytaczać. Nie odnosi się do artykułu, ale można go znaleźć w pliku md5hash.cpp
W tym miejscu, w prosty sposób przedstawimy funkcję getMD5, która obejmuje wskaźnik na dane, ich rozmiar i wskaźnik na bufor z 16 bajtów oraz gdzie będzie zapisany hash.

Kod programu znajduje się w pliku avrec.cpp

Najpierw połączymy wszystkie niezbędne pliki i określimy funkcję MD5, a także napiszemy funkcję wspomagającą, która będzie potrzebna przy analizie argumentów programu.

Listing :
 
// - Niezbędne uruchomienia
#include <stdlib.h>
#include <iostream>
#include <fstream>
#include <windows.h>
#include <avrecord.h>
#include <AVBFile.h>
 
using namespace std;
 
 
//! Kopiowanie argumentu
bool copyArg(PCSTR Arg, DWORD Offset, PSTR Buffer, DWORD Size){
        int ArgLen = strlen(Arg) - Offset;
        if(ArgLen > Size - 1 || ArgLen <= 0) return false;
        memcpy(Buffer, (void*)((DWORD)Arg + Offset), ArgLen);
        Buffer[ArgLen] = 0x00;
}
 
void getMD5(const void* pData, size_t nDataSize, PDWORD RetHash);
 


Po kolei przeanalizujemy główną funkcję main. W niej znajduje się cały przydatny kod.

Najpierw przeprowadzana jest analiza argumentów:
Listing :
 
        cout << endl;
        // - Sprawdzenie ilości argumentów
        if(argc < 2){
                cout << "ttAvRec v1.0 by Av-School.ru" << endl;
                cout << endl;
                cout << "    Usage:" << endl;
                cout << "        avrec.exe [OPTIONS...]" << endl;
                cout << endl;
                cout << "    Options:" << endl;
                cout << "        -s[PATH]    Source infected file"      << endl <<
                                "        -d[PATH]    Output database file"    << endl <<
                                "        -o[NUM]     Signature offset"                << endl <<
                                "        -l[NUM]     Signature size"          << endl <<
                                "        -n[NAME]    Record name"           << endl;
                cout << endl;
                return 0;
        }
 
        // - Obiekt nowego zapisu
        SAVRecord       Record;
        CHAR            SrcFile[256];
        CHAR            DstFile[256];
 
        // - Analiza argumentów
        for(int ArgID = 1; ArgID < argc; ArgID++){
                // - Ścieżka wyjściowa
                if(!memcmp(argv[ArgID], "-s", 2)){
                        if(!copyArg(argv[ArgID], 2, SrcFile, sizeof(SrcFile))){
                                cout << "> Error in -s argument. Stop" << endl;
                                return 0;
                        }
                // - Plik bazy
                }else if(!memcmp(argv[ArgID], "-d", 2)){
                        if(!copyArg(argv[ArgID], 2, DstFile, sizeof(SrcFile))){
                                cout << "> Error in -d argument. Stop" << endl;
                                return 0;
                        }
                // - Modyfikacja sygnatury
                }else if(!memcmp(argv[ArgID], "-o", 2)){
                        Record.Signature.Offset = atoi((PCSTR)((DWORD)argv[ArgID] + 2));
 
                // - Rozmiar sygnatury
                }else if(!memcmp(argv[ArgID], "-l", 2)){
                        Record.Signature.Lenght = atoi((PCSTR)((DWORD)argv[ArgID] + 2));
                // - Nazwa
                }else if(!memcmp(argv[ArgID], "-n", 2)){
                        int NameLen = strlen(argv[ArgID]) - 2;
                        if(NameLen <= 0){
                                cout << "> Error in -n argument. Stop" << endl;
                                return 0;
                        }
                        Record.allocName(NameLen);
                        copyArg(argv[ArgID], 2, Record.Name, NameLen + 1);
                }
        }
 


Jeśli podano zbyt mało argumentów, to pojawi się informacja na temat wykorzystania programu. W przeciwnym razie ma miejsce ich "identyfikacja".
Funkcja copyArg kopiuje we wskazane miejsce wartość argumentu bez klucza.

Po tym, jak dane o zapisie zostały otrzymane, można przystąpić do obliczeń sumy kontrolnej sygnatury.

Listing :
 
        //
        // - Otworzenie pliku wyjściowego
        ifstream        hSrcFile;
        hSrcFile.open(SrcFile, ios::in | ios::binary);
        if(!hSrcFile.is_open()){
                cout << "> Can't open source file. Stop." << endl;
                return 0;
        }
        // - Odczyt danych dla licznika sumy kontrolnej
        PBYTE   Buffer = new BYTE[Record.Signature.Lenght];
        if(Buffer == NULL){
                cout << "> Can't alloc memory for sign data. Stop." << endl;
                hSrcFile.close();
                return 0;
        }
        hSrcFile.seekg(Record.Signature.Offset, ios::beg);
        hSrcFile.read((PSTR)Buffer, Record.Signature.Lenght);
        // - Zamknięcie wyjściowego pliku
        hSrcFile.close();
        // - Licznik hasha sygnatury
        getMD5(Buffer, Record.Signature.Lenght, Record.Signature.Hash);
       
        // - Opróżnienie buforu
         Buffer;
 


Najpierw otwieramy plik, a nastepnie przechodzimy do wskazanej zmiany i obliczamy hash.

Na koniec, do pliku bazy dodajemy zapis, przy okazji zapisując informację w konsoli.
W tym celu wykorzystujemy klasę CAVBFileWriter
Listing:
 
        // -
        // - Dodanie sygnatury
        cout << "Record info:" << endl;
        printf( "    Name:         %sn", Record.Name);
        printf( "    Offset:       0x%x (%d)n", Record.Signature.Offset, Record.Signature.Offset);
        printf( "    Lenght:       0x%x (%d)n", Record.Signature.Lenght, Record.Signature.Lenght);
        printf( "    CheckSumm:    0x%x%x%x%xn", Record.Signature.Hash[0], Record.Signature.Hash[1], Record.Signature.Hash[2], Record.Signature.Hash[3]);
        CAVBFileWriter  hAVBFile;
        hAVBFile.open(DstFile);
        if(!hAVBFile.is_open()){
                cout << "> Can't open database file. Stop." << endl;
                return 0;
        }
        hAVBFile.addRecord(&Record);
        hAVBFile.close();
       
        cout << "Record added." << endl;
 
        return 0;
 


Program gotowy! Można kompilować. :)
A póki kompiluje, przejdźmy do napisania samego skanera!




W końcu dotarliśmy do sedna sprawy.
Na razie skaner będzie w prosty sposób sprawdzać, czy dany plik jest szkodliwy czy nie.
Leczenie, usuwanie, kwarantannę odłóżmy na później.
Plik z bazą i skaner powinny znajdować się we wspólnym folderze. Plik powinien nosić nazwę avbase.avb.
Program przyjmuje jeden jedyny parametr - ścieżkę do folderu, w którym należy przeprowadzić sprawdzenie.
Skaner będzie zawierał więcej kodu, ale całość generalnie nie przysporzy problemów

Algorytm pracy:
1. Pobranie pliku bazy
2. Pobranie listy plików w podanym folderze.
3. Jeśli jest to plik - sprawdzamy. Jeśli folder - przechodzimy do punktu 2.

Pobranie pliku bazy będzie odbywać się w specjalnej strukturze SAVRecordCollection.

Sprawdzenie pliku sprowadza się do prostej analizy wszytkich sygnatur.
Jeśli sygnatura jest obecna, to informujemy, że z plikiem jest coś nie tak. W przeciwnym razie - że wszystko w porządku.

Listing :
 
#include <stdlib.h>
#include <iostream>
#include <fstream>
#include <windows.h>
 
#include <avrecord.h>
#include <AVBFile.h>
 
 
using namespace std;
 
//! Zbiór zapisów
typedef struct SAVRecordCollection{
        SAVRecordCollection(DWORD RecordCount){
                this->RecordCount    = RecordCount;
                this->Record         = new SAVRecord[this->RecordCount];
        }
        ~SAVRecordCollection(){
                []      this->Record;
        }
        DWORD           RecordCount;
        PSAVRecord      Record;
} * PSAVRecordCollection;
 
// - Zbiór zapisów
PSAVRecordCollection            AVRCollection   = NULL;
 
void    processPath(PCSTR Path);
void getMD5(const void* pData, size_t nDataSize, PDWORD RetHash);
 
 


Funkcja processPath będzie rozpatrzona niżej.
Najpierw przeprowadzimy standardową analizę argumentów:

Listing :
 
        if(argc < 2){
                cout << "ttAVScan v1.0" << endl;
                cout << endl;
                cout << "    Usage:" << endl;
                cout << "        avscan.exe [Path]" << endl;
                cout << endl;
                cout << "    Arguments:" << endl;
                cout << "        Path    - Dirrectory to scan" << endl;
                cout << endl;
                return 0;
        }
 
        PCSTR   SrcPath = argv[1];      // - Ścieżka skanowania
 
 
        // - Otrzymanie ścieżki do pliku i bazy
        CHAR    AVBPath[MAX_PATH];      // - Ścieżka do folderu z programem
        memset(AVBPath, 0, MAX_PATH);
        PCHAR   NamePtr;
        GetFullPathNameA(argv[0], MAX_PATH, AVBPath, &NamePtr);
        *NamePtr = 0x00;
        strcat_s(AVBPath, MAX_PATH, "avbase.avb");
       
 


Funkcja GetFullPathName w trzecim parametrze zwraca wskaźnik do nazwy pliku wykonawczego, na początku którego wpisujemy "\0" (bez cudzysłowu). W ten sposób odrzucając go zostawia tylko ścieżkę do folderu, w którym znajduje się plik wykonawczy.

Następnym krokiem będzie pobranie bazy.

Listing:
 
 
        // - Załadowanie zapisów
        cout << endl;
        cout << "Loading bases...";
        CAVBFileReader  hAVBFile;
        if(!hAVBFile.open(AVBPath)){
                cout << "Can't open AV Bases file. Stop." << endl;
                return 0;
        }
        if(hAVBFile.getRecordCount() > 0){
                // - Stworzenie bazy
                AVRCollection = new SAVRecordCollection(hAVBFile.getRecordCount());
                for(DWORD RecID = 0; RecID < AVRCollection->RecordCount; RecID++){
                        if(!hAVBFile.readNextRecord(&AVRCollection->Record[RecID])){
                                cout << "> Error loading record #" << RecID << endl;
                        }
                }
                hAVBFile.close();
        }else{
                hAVBFile.close();
                cout << "> Empty AV Base. Stop." << endl;
                return 0;
        }
        cout << "t" << AVRCollection->RecordCount << " records loaded." << endl;
       
        //
        cout << endl;
        cout << "Starting scan for viruses" << endl;
        cout << endl;
 
        processPath(SrcPath);
 
 


Otwieramy plik, wyznaczamy pamięć na wpisy, następnie odczytujemy informację z pliku.
Jeśli wszystko przebiegło pomyślnie, to pojawi się funkcja processPath, która wykonuje rekursywne sprawdzenie na ukazanej ścieżce.

Funkcja wygląda tak:

Listing :
 
 
void    processPath(PCSTR Path){
        string  SrcPath = Path;
        string  File;
        File    = Path;
        File    += "*.*";
 
        WIN32_FIND_DATAA        FindData;       
        HANDLE hFind = FindFirstFileA(File.c_str(), &FindData);
 
        do{
                // - Pomijamy foldery . i ..
                if(!strcmp(FindData.cFileName, ".") || !strcmp(FindData.cFileName, "..")) continue;
 
                File = Path;
                File += "";
                File += FindData.cFileName;
 
                // - Jeśli folder, skanujemy rekursywnie
                if((FindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)){
                        processPath(File.c_str());
                // - Inaczej sprawdzamy wirusy
                }else{
                        checkFile(File.c_str());
                }
 
        } while(FindNextFileA(hFind, &FindData));
 
 
}
 


Otrzymujemy listę plików i folderów (poza folderami "." i ".."), przy czym jeśli dostaliśmy folder, to wykonujemy rekursywny przegląd. A jeśli otrzymaliśmy plik, spradzamy jego funkcję checkFile.

Poniżej listing funkcji checkFile

Listing :
 
 
void    checkFile(PCSTR FileName){
        cout << FileName << "t";
        // - Otwieramy plik
        HANDLE  hFile = CreateFileA(FileName, FILE_READ_ACCESS, NULL, NULL, OPEN_EXISTING, NULL, NULL);
        if(hFile == INVALID_HANDLE_VALUE){
                cout << "Error" << endl;
                return;
        }
        // - Otrzymujemy rozmiar pliku
        DWORD FileSize = GetFileSize(hFile, NULL);
 
        // - Odtwarzamy plik w pamięci
        HANDLE  hMap = CreateFileMappingA(hFile, NULL, PAGE_READONLY, NULL, FileSize, NULL);
        if(hFile == INVALID_HANDLE_VALUE){
                cout << "Error" << endl;
                CloseHandle(hFile);
                return;
        }
        LPVOID  File = MapViewOfFile(hMap, FILE_MAP_READ, NULL, NULL, FileSize);
        if(File == NULL){
                cout << "Error" << endl;
                CloseHandle(hMap);
                CloseHandle(hFile);
                return;
        }
       
        // - Wyszukiwanie według sygnatur
        bool    Detected = false;
        for(DWORD RecID = 0; RecID < AVRCollection->RecordCount; RecID++){
                PSAVRecord      Record = &AVRCollection->Record[RecID];
                // - Jeśli plik jest za mały, to pomijamy zapis
                if(FileSize < (Record->Signature.Offset + Record->Signature.Lenght)) continue;
                // - Obliczamy sumę kontrolną dla sygnatury
                DWORD   Hash[4];
                getMD5((PBYTE)((DWORD)File + Record->Signature.Offset), Record->Signature.Lenght, Hash);
 
                // - Sprawdzamy
                if(!memcmp(Hash, Record->Signature.Hash, 4 * sizeof(DWORD))){
                        cout << " DETECTEDt" << Record->Name << endl;
                        Detected = true;
                        break;
                }
        }
 
        UnmapViewOfFile(File);
        CloseHandle(hMap);
        CloseHandle(hFile);
 
        if(!Detected)   cout << "OK" << endl;
}
 
 


Omówimy ją bardziej szczegółowo.
Po pierwsze, w funkcji zamiast odczytu pliku wykorzystano obraz pliku w pamięci, przy którym plik zmienia się w adresową przestrzeń procesu, i dla dostępu do pliku nie wymaga przeprowadzenia operacji odczytu lub zapisu.
Ta metoda została wybrana, ponieważ przy sprawdzaniu sygnatur wymaga ciągłego przemieszczania sie w obrębie pliku zgodnie ze zmianą sygnatur.
Przemieszczenie na tablicy odbywa się o wiele szybciej niż w pliku. Także dla obliczeń hasha wystarczy przekazać wskaźnik na początek sekwencji.
Przy standardowym podejściu, za każdym razem byłoby konieczne odczytywanie informacji z pliku, co jest nie tylko wolne, ale po prostu niewygodne.

Funkcja MapViewOfFile zwraca adres, począwszy od tego, z którego był otworzony plik.
Adres ten jest początkiem pliku, lub jeśli przedstawiać plik jako tablicę, to dany adres będzie jej początkiem.

Wyszukiwanie sygnatury wykonujemy w następujący sposób:
Wszystkie wpisy ze zbioru przeglądane są w cyklu.
Jeśli dla wpisu suma zmiany sygnatury i jej rozmiar są mniejsze od rozmiaru pliku (sygnatura mieści się w pliku), to zachodzi hashowanie sekwencji danych.
Następnie porównuje się otrzymany hash z hash'em z sygnatury.
Jeśli są zgodne, to plik będzie rozpoznany jako niebezpieczny (lub fałszywy alarm)

Pozostała już tylko kompilacja i test.

Listing :
 
avrec.exe -sVirus.vbs -davbase.avb -o253 -l280 -nVirus.VBS.Baby
 


Sprawdzamy:


Plik rzeczywiście działa!



TEKST +   TEKST -  
Komentarze (0)
Brak komentarzy
Dodaj komentarz
Aby dodać komentarz musisz być zalogowany.
Patronat
Partnerzy
Wydarzenia
Ostatnio
Electronic Games Party 2012
Dzisiaj
Brak
Najbliższe
Brak
Statystyki zagrożeń
TOP 10 złośliwych
programów tygodnia

1. Malicious URL
2. Script.Iframer Troj
3. Win32.Generic Troj
4. Script.Generic Troj
5. Script.Generic Exp
6. Shopper.il AdW
7. Script.Generic TrD
8. Eorezo.heur AdW
9. Popupper.aw Troj
10. Shopper.jq AdW

Żródło: KSN
Imieniny
Czy wiesz, że...
3G - 3G (skrót terminu "trzecia generacja") to ogólne pojęcie oznaczające technologie i standardy, które łączą szybki dostęp mobilny z usługami opartymi na IP. 3G usprawnia usługi bezprzewodowe,...
Z galerii...
Wizytówki drużyn - jesień 2011

Polecamy artykuł
Fałszywe antywirusy - ataki pod pozorem ochrony
     Główna            Kontakt           O projekcie
Copyright © 2012 Kaspersky Lab