hur man gör en antivirusmotor
introduktion
när jag roaming runt techies forum ser jag ofta vissa människor (och många inte särskilt erfarna) som frågar efter ”hur gör jag ett antivirusprogram”, ibland med inte särskilt anpassade språk (bat, PHP,…) och har en felaktig uppfattning om vad ett antivirusprogram är och hur det ska byggas.
jag har också sett många ”Antivirusprogramvaror” gjorda av kiddies, med väldigt få still-at-school människor och cirka 4 timmar per dag av kodning på flera veckor. Jag säger inte att kiddies inte är skickliga, men jag säger att bygga en antivirusmotor behöver antingen många skickliga människor med heltidsjobb plus mycket tid att släppa en anständig programvara eller mycket pengar för att betala dem bisexuell (om de inte är frivilliga).
så jag täcker här riktlinjerna för en grundläggande antiviruskodning, för Windows och i C/C++. Man kan hitta här pekarna för att designa en antivirusmotor, eller helt enkelt lära sig hur de flesta är byggda.
skydd
för ett bra skydd måste ett antivirusprogram ha minst en drivrutin för att kunna köra kod i kärnan och totalt sett ha tillgång till kernel API: er. Från och med Vista förstod Microsoft att Antivirusindustrin behövde nycklar för att komma in i kärnan och aktivera filter på strategiska platser, till exempel Filsystem, register och nätverk. Bli inte bedövad om att bygga ett antivirusprogram för Pre-Vista-system kan vara en verklig smärta, eftersom det inte var utformat för detta.
- men på pre-Vista-system brukade antivirusföretag använda rootkit – liknande funktioner för att skydda dörrarna (även om det inte rekommenderades alls av Microsoft) och kunna skydda ditt system. De använde vad vi kallar” krokar ” (API-omvägar för filtreringsändamål).
- på Vista+ tillhandahöll Microsoft API: er för att infoga vår lågnivådrivrutin mellan userland-samtal och kernel-API: er. På så sätt är det enkelt att registrera en antivirusprodukt i kärnan. Mer, det typregistreringsbaserade Systemet tillåter oss att skicka vår systemsäkerhet i lager, där flera produkter med olika mål kan sambo. Detta var inte fallet för krokar, eftersom genomförandet var helt produktberoende.
notera: Jag kommer inte att täcka lösningarna med krokar för Pre-Vista-system, eftersom det är lätt att hitta på internet och eftersom det skulle behöva ett helt kapitel för att förklara hur man hakar, var man hakar och så… men du måste veta att det är samma ide än kärnan API, förutom att du måste implementera dig själv vad Microsoft tillhandahöll på Vista+ – system.
för att lära dig om kodningsdrivrutiner kan du kontrollera att användbara länkar:
http://msdn.microsoft.com/en-us/library/windows/hardware/gg490655.aspx
http://www.codeproject.com/Articles/9504/Driver-Development-Part-1-Introduction-to-Drivers
om du vill veta mer om krokar, du kan kontrollera att grundläggande exempel:
http://www.unknowncheats.me/forum/c-and-c/59147-writing-drivers-perform-kernel-level-ssdt-hooking.html
Process
det första att skydda användaren från, är lanseringen av skadliga processer. Detta är den grundläggande saken. Antivirus bör registrera en pssetcreateprocessnotifyroutineex återuppringning. Genom att göra detta, på varje process skapande, och innan huvudtråden börjar springa (och orsaka skadliga saker) antivirus återuppringning meddelas och tar emot all nödvändig information.
det tar emot processnamnet, filobjektet, PID och så. Eftersom processen väntar kan föraren berätta för sin tjänst att analysera processens minne för något skadligt. Det det grundar något, föraren kommer helt enkelt att ställa CreationStatus till falskt och återvända.
NTSTATUS PsSetCreateProcessNotifyRoutineEx( _In_ PCREATE_PROCESS_NOTIFY_ROUTINE_EX NotifyRoutine, _In_ BOOLEAN Remove);
VOID CreateProcessNotifyEx( _Inout_ PEPROCESS Process, _In_ HANDLE ProcessId, _In_opt_ PPS_CREATE_NOTIFY_INFO CreateInfo);
typedef struct _PS_CREATE_NOTIFY_INFO { SIZE_T Size; union { ULONG Flags; struct { ULONG FileOpenNameAvailable :1; ULONG Reserved :31; }; }; HANDLE ParentProcessId; CLIENT_ID CreatingThreadId; struct _FILE_OBJECT *FileObject; PCUNICODE_STRING ImageFileName; PCUNICODE_STRING CommandLine; NTSTATUS CreationStatus;} PS_CREATE_NOTIFY_INFO, *PPS_CREATE_NOTIFY_INFO;
trådar
i samma ide än för processer kan trådar vara ett sätt för skadliga saker att orsaka skador. Till exempel kan man injicera någon kod i en legit process och starta en fjärrtråd på den koden i processens sammanhang (lätt att följa? 🙂 ). På så sätt kan en legit process göra skadliga saker.
vi kan filtrera nya trådar med pssetcreatethreadnotifyroutine återuppringning. Varje gång en tråd skapas meddelas antivirusprogrammet med TID och PID. Således kan den titta på trådens startadresskod, analysera den och antingen stoppa tråden eller återuppta den.
NTSTATUS PsSetCreateThreadNotifyRoutine( _In_ PCREATE_THREAD_NOTIFY_ROUTINE NotifyRoutine);
VOID(*PCREATE_THREAD_NOTIFY_ROUTINE) ( IN HANDLE ProcessId, IN HANDLE ThreadId, IN BOOLEAN Create );
bilder
det tredje dynamiska hotet handlar om bilder som kan laddas i minnet. En bild är en PE-fil, antingen en EXE, en DLL eller SYS-fil. För att bli underrättad om laddade bilder, helt enkelt registrera PsSetLoadImageNotifyRoutine. Den återuppringningen gör att vi kan meddelas när bilden laddas i virtuellt minne, även om den aldrig körs. Vi kan sedan upptäcka när en process försöker ladda en DLL, ladda en drivrutin eller avfyra en ny process.
återuppringningen får information om hela bildvägen (användbar för statisk analys), och desto viktigare enligt min mening, Bildbasadressen (för analys i minnet). Om bilden är skadlig kan antivirusprogrammet använda små knep för att undvika körning, som att analysera bilden i minnet och gå till ingången, ring sedan monteringsopkoden ”ret” för att upphäva den.
NTSTATUS PsSetLoadImageNotifyRoutine( _In_ PLOAD_IMAGE_NOTIFY_ROUTINE NotifyRoutine);
VOID (*PLOAD_IMAGE_NOTIFY_ROUTINE)( __in_opt PUNICODE_STRING FullImageName, __in HANDLE ProcessId, __in PIMAGE_INFO ImageInfo );
typedef struct _IMAGE_INFO { union { ULONG Properties; struct { ULONG ImageAddressingMode : 8; //code addressing mode ULONG SystemModeImage : 1; //system mode image ULONG ImageMappedToAllPids : 1; //mapped in all processes ULONG Reserved : 22; }; }; PVOID ImageBase; ULONG ImageSelector; ULONG ImageSize; ULONG ImageSectionNumber;} IMAGE_INFO, *PIMAGE_INFO;
filsystem
när varje dynamisk sak är säkrad, ett antivirusprogram ska kunna meddela användaren för skadliga saker on-the-fly, inte bara när de är på väg att starta. Ett antivirusprogram ska kunna skanna filer när användaren öppnar en mapp, ett arkiv eller när det laddas ner på disken. Mer, ett antivirusprogram ska kunna skydda sig genom att förbjuda något program att ta bort sina filer.
sättet att göra allt detta är att installera en drivrutin i filsystemet, och mer specifikt en minifilter av ett äldre filter (old way). Här kommer vi att prata om minifilter.
ett minifilter är en specifik typ av drivrutin som kan registrera återuppringningar på varje läs – /skrivoperation som görs på filsystemet (IRP-huvudfunktioner). En IRP (Interrupt Request Paquet) är ett objekt som används för att beskriva en läs – /skrivoperation på skivan, som överförs tillsammans med drivrutinsstacken. Minifiltret kommer helt enkelt att sättas in i den stacken och ta emot den IRP för att bestämma vad man ska göra med det (Tillåt/neka operation).
för ett litet exempel på minifilter, kontrollera den användbara länken eller den. Microsofts riktlinjer finns här.
du hittar också 2 Exempel på WDK dokumentation här och här.
en grundläggande minifilter återuppringning ser ut så här. Det finns 2 typer av återuppringning, före operation och efter operation, som kan filtrera före av efter frågan. Här är en preoperation pseudokod:
FLT_PREOP_CALLBACK_STATUS PreOperationCallback (__inout PFLT_CALLBACK_DATA Data, __in PCFLT_RELATED_OBJECTS FltObjects, __deref_out_opt PVOID *CompletionContext){ ... if ( all_good ) { return FLT_PREOP_SUCCESS_NO_CALLBACK; } else { // Access denied Data->IoStatus.Information = 0; Data->IoStatus.Status = STATUS_ACCESS_DENIED; return FLT_PREOP_COMPLETE; } }
registret
registret är en av de mest kritiska platsen att skydda. Det finns många många sätt för en skadlig kod för att hålla ihållande hand på systemet genom att registrera en enda (eller några) nycklar/värden i registret. De mest kända platserna är környcklar och tjänster. Det här är också den plats där antivirusprogrammet kan besegras (tillsammans med filsystemet), genom att helt enkelt ta bort drivrutinen/servicetangenterna, så att det inte längre startar om vid systemstart.
Detta är inte en verklig nödvändighet för ett antivirusprogram för att skydda omstart platser, de flesta av dem inte. men de måste skydda sina installations registernycklar, för att undvika att besegras lätt av malwares. Detta kan göras genom att registrera CmRegisterCallback.
återuppringningen ger tillräckligt med information för att få hela nyckelnamnet, typen av åtkomst (skapa, byta namn, ta bort, … ) och den som ringer PID. På så sätt är det enkelt att ge åtkomst eller inte till samtalet genom att ställa in statusfältet för återuppringning efter Operation.
NTSTATUS CmRegisterCallbackEx(_In_ PEX_CALLBACK_FUNCTION Function,_In_ PCUNICODE_STRING Altitude,_In_ PVOID Driver,_In_opt_ PVOID Context,_Out_ PLARGE_INTEGER Cookie,_Reserved_ PVOID Reserved);
EX_CALLBACK_FUNCTION RegistryCallback;NTSTATUS RegistryCallback( _In_ PVOID CallbackContext, _In_opt_ PVOID Argument1, _In_opt_ PVOID Argument2){ ... }Argument1 = typedef enum _REG_NOTIFY_CLASS { RegNtDeleteKey, RegNtPreDeleteKey = RegNtDeleteKey,...Argument2 = typedef struct _REG_POST_OPERATION_INFORMATION { PVOID Object; NTSTATUS Status; PVOID PreInformation; NTSTATUS ReturnStatus; PVOID CallContext; PVOID ObjectContext; PVOID Reserved;} REG_POST_OPERATION_INFORMATION, *PREG_POST_OPERATION_INFORMATION;
nätverk (brandvägg)
för att skydda dörrarna till hela internettrafiken som kan vara enorm på vissa system (servrar, stora bandbreddsanvändare) utan att bromsas av kontextväxlingen som sker i userland rekommenderas det inte att installera en brandvägg som inte har någon underliggande drivrutin, förutom vissa webbläsarfilter som kan räcka för http-trafik, men som inte skyddar mot skadlig Kommunikation in/ut.
för att få en korrekt implementering av brandväggen bör man koda en ndis, TDI eller en annan metod för lågnivå IP-filtreringsdrivrutin. NDIS / TDI är lite knepigt att göra, och skulle kräva mycket kunskap (mer än andra filter enligt min mening).
hur som helst, här är några tips för att börja koda en sådan drivrutin, microsoft-riktlinjerna och gammal codeproject-handledning (men fortfarande bra att läsa), ett exempel på ndis-brandväggen och ett exempel på TDI-brandväggen. Här är också ett bra skrivande om ndis firewall bypass trick, och en liten förklaring om nätverksdrivrutinstacken,
Userland protection
userland protection är inte en nödvändighet, men kan vara en extra modul mot trojanska bankirer, och mer specifikt mot process spioner. De injiceras vanligtvis i varje process av flera skäl.
först kan de (på begäran) döda processen om den har identifierats som skadlig kod (detta borde inte hända, eftersom AVs ska stoppa det innan det börjar). Det är alltid lättare att stoppa en process när du är i sitt sammanhang.
andra de kan skydda kritisk process, som webbläsare, mot hooking malwares kunna omväg och filtrera API-samtal för att samla lösenord, bankinformation, och omdirigera internetflödet till malware servrar. De tittar bara på IAT-modifiering, för skarvning, och kan också ställa in krokar själva för att undvika LoadLibray av en malware DLL, och därmed förbjuda vissa metoder för kodinjektion.
ett enkelt sätt att injicera en protector DLL i alla processer är att använda AppInitDll registernyckeln för att registrera protector DLL. Det kommer att ladda DLL-filen i varje process som startas på systemet, så snart de länkar Användaren32.dll bild (de flesta av dem gör).
analysmotor
analysmotorn är en av de viktigaste delarna, den ansvarar för att analysera fil/minnesprover som kommer från drivrutinerna. Om måste vara snabb( även med en enorm databas), och bör kunna hantera de flesta av de filtyper( själv extraherade körbara, arkiv-RAR, ZIP, packade filer-UPX,…) och därmed bör ha många moduler för att göra detta:
- Unpacker: den modulen måste kunna upptäcka och packa upp de flesta kända packers (UPX, Armadillo, ASPack, …)
- Signaturmotor: databasen för ett antivirusprogram innehåller miljontals signaturer, och motorn ska kunna snabbt söka efter dem i ett prov. Således bör en mycket kraftfull algoritm vara en del av den. Några exempel : AhoCorasick, RabinKarp, strängmatchningsalgoritmer.
- Sandbox: den modulen är inte nödvändig, men skulle vara ett plus för att kunna köra prover i ett begränsat minne, utan effekt på systemet. Det kan hjälpa till att packa upp prover packade med okända packers och hjälpa den heuristiska motorn (se efter) att upptäcka API-samtal som kan betraktas som misstänkta eller skadliga. Några bra sandlåda här.
- heuristisk motor : som sagt ovan söker den heuristiska motorn inte efter signaturer utan letar snarare efter misstänkt beteende (dvs. exempel som öppnar en anslutning på webbplatsen hxxp: / / malware_besite.com). Det kan göras genom statisk analys eller genom en sandlåda.
Signatursyntax
signatursyntaxen är ”ordboken” för det språk som signaturmotorn förstår. Det är ett sätt att formalisera vad som är mönstret att hitta, hur man söker efter det och var man ska söka i provet. Syntaxen måste vara enkel nog för att forskarna ska förstå, tillräckligt kraftfull för att hantera alla användningsfall och lätt att analysera för bättre motorprestanda.
VirusTotal har utvecklat en bra syntax och motor (Yara-projekt), som är öppen källkod. Det borde vara en bra pekare för att skapa din egen syntax, eller helt enkelt använda den. Här är också ett bra inlägg blogg om hur Skapa signaturer för antivirus.
exempel på Signatur:
rule silent_banker : banker{ meta: description = "This is just an example" thread_level = 3 in_the_wild = true strings: $a = {6A 40 68 00 30 00 00 6A 14 8D 91} $b = {8D 4D B0 2B C1 83 C0 27 99 6A 4E 59 F7 F9} $c = "UVODFRYSIHLNWPEJXQZAKCBGMT" condition: $a or $b or $c}
självskydd
självskyddet är mycket viktigt för ett antivirusprogram, för att undvika att besegras av en skadlig kod och fortsätta att skydda användaren. Det är därför ett antivirusprogram ska kunna skydda sin egen installation och hålla uthållighet vid omstart.
det finns flera ställen att skydda: filer, registernycklar, processer/trådar, minne.
- Filskydd implementeras i minifiltret, med särskilda regler för antivirusfilerna (ingen åtkomst vid radering, byte av namn, flyttning, skrivning).
- Registerskydd görs i registerfiltret, med Åtkomst nekad för registernycklar för föraren och tjänsten.
- drivrutinstrådarna är skyddade, eftersom det är ganska omöjligt att lossa kärnmodulen utan att krascha systemet
- för att kunna skydda tjänsten, vilket är en användarprocess, 2-lösningar:
- det enklaste skulle vara att lägga till regler för fel i servicehanteraren och ställa in varje felregel till ”starta om tjänsten”. På det sättet, när tjänsten inte stoppas av service manager, startar den om. Naturligtvis bör tjänsten inte kunna acceptera kommandon förrän systemet inte startar om (eller stoppar).
- den andra metoden, som är mer generisk, skulle vara att ställa in återuppringningar på processhandtag med ObRegisterCallbacks.
genom att ställa in ObjectType till PsProcessType och operationen till OB_OPERATION_HANDLE_CREATE får du en återuppringning före och efter operationen, och du kan returnera ACCESS_DENIED till Returstatus om processhandtaget frågat har beviljat åtkomst som har process avsluta rättigheter (eller process skrivrättigheter, eller något som kan leda till en krasch/döda), och naturligtvis om processen är en måste antiviruset skydda (dess tjänst till exempel).
naturligtvis måste man också skydda Dubbletthandtag och PsThreadType för att undvika någon uppsägningsmetod som kräver att man tar ett handtag på processen eller en tråd. Här är ett litet exempel på användning av den återuppringningen.
NTSTATUS ObRegisterCallbacks( _In_ POB_CALLBACK_REGISTRATION CallBackRegistration, _Out_ PVOID *RegistrationHandle);
typedef struct _OB_CALLBACK_REGISTRATION { USHORT Version; USHORT OperationRegistrationCount; UNICODE_STRING Altitude; PVOID RegistrationContext; OB_OPERATION_REGISTRATION *OperationRegistration;} OB_CALLBACK_REGISTRATION, *POB_CALLBACK_REGISTRATION;
typedef struct _OB_OPERATION_REGISTRATION { POBJECT_TYPE *ObjectType; OB_OPERATION Operations; POB_PRE_OPERATION_CALLBACK PreOperation; POB_POST_OPERATION_CALLBACK PostOperation;} OB_OPERATION_REGISTRATION, *POB_OPERATION_REGISTRATION;
VOID ObjectPostCallback( _In_ PVOID RegistrationContext, _In_ POB_POST_OPERATION_INFORMATION OperationInformation);
typedef struct _OB_POST_OPERATION_INFORMATION { OB_OPERATION Operation; union { ULONG Flags; struct { ULONG KernelHandle :1; ULONG Reserved :31; }; }; PVOID Object; POBJECT_TYPE ObjectType; PVOID CallContext; NTSTATUS ReturnStatus; POB_POST_OPERATION_PARAMETERS Parameters;} OB_POST_OPERATION_INFORMATION, *POB_POST_OPERATION_INFORMATION;
typedef union _OB_POST_OPERATION_PARAMETERS { OB_POST_CREATE_HANDLE_INFORMATION CreateHandleInformation; OB_POST_DUPLICATE_HANDLE_INFORMATION DuplicateHandleInformation;} OB_POST_OPERATION_PARAMETERS, *POB_POST_OPERATION_PARAMETERS;
typedef struct _OB_POST_CREATE_HANDLE_INFORMATION { ACCESS_MASK GrantedAccess;} OB_POST_CREATE_HANDLE_INFORMATION, *POB_POST_CREATE_HANDLE_INFORMATION;
GUI (Graphical User Interface)
detta är den synliga delen av isberget. Enligt min mening en (kanske den) viktigaste delen om du vill sälja din produkt. Användare älskar det som är vackert, lätt att använda, intuitivt. Även om det inte är 100% effektivt. GUI måste vara sexig.
GUI är bara ett tomt skal, det gör bara grafiska behandlingar och skickar/tar emot kommandon till kärnan (tjänsten). Det visar också förloppsindikatorer, vad som analyseras, ger konfiguration, och så… här är Avast UI. Sexig, eller hur? 🙂
arkitektur
den globala arkitekturen kan vara något som ser ut så här:
- GUI: inga administrativa rättigheter, svag
- Guard DLL(s) : webbläsare skydd, MEDIUM
- Service : administratörsrättigheter. Fungerar som en inkörsport till kärnkod och fatta beslut tillsammans med vissa databas, STRONG
- Driver(s) : Kernel filter, STRONG
GUI behöver inte någon administrativ rätt, det tar bara användaråtgärder och överför dem till tjänsten. Det visar också produktstatus. Inget mer, Detta är inte dess mål. Om GUI dödas är detta inte ett problem eftersom tjänsten ska kunna starta om den.
guard DLLs (om någon) injiceras massivt i alla processer och bör kunna leta efter IAT-krokar och/eller skadliga trådar. De borde vara ganska svåra att lossa eller besegra. De är inte kritiska men viktiga.
tjänsten är kärnan i produkten. Det borde vara unkillable, eller åtminstone borde kunna starta om på kill. Tjänsten ansvarar för kommunikation mellan alla moduler i produkten, den skickar kommandon till drivrutiner, tar kommandon från användaren och frågar databasen för provanalys. Det här är hjärnan.
kärndrivrutinerna är också kritiska. De är tentaklarna som samlar information om allt som händer på systemet och överför dem till tjänsten för beslut. De kan också neka tillgång till bevakade platser, baserat på tjänstens beslut.
slutsats
att göra en stark, pålitlig och stabil antivirusmotor är en komplicerad uppgift, som behöver experimenterade människor med en mycket stark kunskap i Windows kernel programmering, Windows application programming, GUI design, mjukvaruarkitektur, malware analys, …
bygga stabila drivrutiner är också en komplicerad uppgift, eftersom en liten sandkorn kan krascha hela systemet. Det behöver testning, testning och mycket testning. När din motor är klar och fungerar behöver du bara anställa forskare för att analysera malwares och lägga till signaturer i din databas. vad väntar du på? Fortsätt!
länkar
- http://www.symantec.com/connect/articles/building-anti-virus-engine