................... ...::: phearless zine #1 :::... ......................>---[ The Art of Sniffing ]---<....................... .........................>---[ by BaCkSpAcE ]---<........................... SADRZAJ: [0] Uvod - Sta je to sniffer? [1] Osnovne funkcije za rad sa socketima <1.1> socket() <1.2> bind() <1.3> recvfrom() <1.4> htons(), htonl(), ntohs(), ntohl() [2] Struktura jednog kompletnog paketa [3] Osnovni elementi konstrukcije <3.1> I faza <3.2> II faza <3.3> III faza <3.4> IV faza [4] Primitivna verzija sniffera [5] Nadogradnja <5.1> I faza <5.2> II faza <5.3> III faza <5.4> IV faza [6] Konacna verzija sniffera [7] Zastita od sniffinga [8] Literatura [9] Odvod /////////////////////////////////////////////////////////////////////////// --==<[ 0. Uvod: Sta je to sniffer? \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ Ah, ti snifferi (u prevodu njuskalo)... Bilo je reci i tekstova o njima i funkionisanju istih, ali je malo bilo kvalitetnih i potpunih. Zato sam se ja potrudio da napisem jedan dooobar tekst sa malo detaljnijom analizom konstrukcije sniffera, i uopste socket programiranja. Jos jedan od razloga nastanka ovog teksta jeste omogucivanje ostalim znatizeljnicima brzeg uvoda u reverse engineering mreznog saobracaja (al' sam mu dao naziv). Izrada i testiranje (delova) source kodova koji su korisceni u ovom tekstu radjena je u linux okruzenju (tacnije kernel 2.4.22). Sama teorija ovog teksta moze da se primeni u bilo kom okruzenju i programskom jeziku, a primeri i biblioteke koje su ovde upotrebljene mogu se koristiti samo u linux okruzenju. Tekst je namenjen naprednijim korisnicima i programerima, tako da je potrebno odredjeno predznanje (osnove linuxa, programiranje u C, tcp/ip protokol, osnove socket programiranja). Snifferi sluze za posmatranje mreznog saobracaja u kojem vi direktno ucestvujete, ili se nalazite u neposrednoj blizini (npr. lokalne mreze). To je analogno drumskom saobracaju, gde vi posmatrate vozila sa nekog mesta, npr. obliznje pivnice. Nasa uloga bi tu bila da pribelezimo svaki automobil i informacije o tome odakle je krenuo, gde ide i sta nosi. Upotreba... mogu se upotrebiti za debug neke client/server aplikacije, za proveru filtera koje smo postavili, odnosno da li pravilno funkcionisu... Kao sto vecina "alata" moze pametno da se iskoristi, isto tako mogu da se upotrebe za "prljave" poslove. Najvise se koriste za prikupljanje passworda koji mogu biti iskorisceni za veoma lagan upad u neki racunar. "Crne kape" vec dobro znaju za sta mogu upotrebiti sniffere, i gde ih je pogodno namestiti. /////////////////////////////////////////////////////////////////////////// --==<[ 1. Osnovne funkcije za rad sa socketima \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ Kao sto sam naveo na pocetku, da bi mogli da pratite tekst nadalje, potrebno vam je bar neko osnovno znanje i iskustvo u socket programiranju. Osnovne i neophodne funkcije koje se koriste pri pravljenju jednog sniffera su socket(), bind() i recvfrom(). Takodje, tu su i nezaobilazne ntohs, ntohl, htons i htonl. Od dodatnih funkcija koje "dobro dodju" mozemo navesti setsockopt() i ioctl(). ---< 1.1 socket() int socket(int domain, int type, int protocol) Pod parametrom domain podrazumevamo izbor familije protokola koji ce biti upotrebljen u komunikaciji, u zavisnosti od tipa mreze. Tu se nalaze: PF_PACKET, PF_INET, PF_INET6, PF_UNIX, PF_IPX... ali nama je od navedenih najzanimljiviji i najkorisniji PF_PACKET jer nam je on, ustvari, low-level interfejs ka celokupnom mreznom saobracaju. Moze koristiti i neki drugi izbor protokola, ali svi drugi nam daju manje mogucnosti i veca ogranicenja za snimanje mreznog saobracaja. Da bi ga koristili, potrebno je da imamo uid 0 ili da imamo CAP_NET_RAW mogucnost. Protocol Family (PF) i Adress Family (AF) su, u sustini, iste stvari, tako da slobodno mozemo pisati AF_PACKET umesto PF_PACKET. Ali prema sintaksi socket() funkcije, prvi argument mora biti neki skup protokola. Sto se tice tipa socketa u zavisnosti od komunikacione semantike, imamo SOCK_STREAM, SOCK_DGRAM, SOCK_RAW, SOCK_PACKET... Necu se zadrzavati na objasnjavanju ostalih tipova socketa, vec samo onih koji nam mogu zatrebati. Posto smo izabrali domen protokola PF_PACKET koji prima/salje pakete na drugom OSI (Open System Interconnection) nivou, onda mozemo da koristimo: SOCK_DGRAM i SOCK_RAW. Razlike izmedju ova dva tipa socketa su minimalne. SOCK_RAW se koristi za raw pakete sa link level headerom, dok SOCK_DGRAM se koristi za raw pakete bez link level headera. Prema tome, moze se reci da je SOCK_DGRAM na nesto visem nivou od SOCK_RAW socketa. Link Level header je u sockaddr_ll formatu, odnosno strukturi, o kojoj ce kasnije biti reci. Parametar protokol predstavlja protokol koji ce se koristiti u komunikaciji. To moze biti IPPROTO_TCP, IPPROTO_UDP... U normalnom slucaju se stavlja obicno vrednost 0, jer ako smo izabrali npr. SOCK_DGRAM socket, onda sigurno primamo samo UDP pakete (IPPROTO_UDP)... A ako bi hteli da radimo samo sa TCP paketima, onda cemo navesti IPPROTO_TCP. Znaci sistem je u mogucnosti sam da odluci sta prima/odbacuje. ---< 1.2 bind() int bind(int s, struct sockaddr *name, int size) Ova funkcija nam sluzi da pomocu vec popunjene sockaddr strukture isfiltriramo mrezni saobracaj koji zelimo da primamo/saljemo. Da bi to uradili, moramo navesti tri parametra: naziv postojeceg socketa, struktura sa kojom cemo da povezemo socket i velicina te strukture. ---< 1.3 recvfrom() ssize_t recvfrom(int s, void *buf, size_t len, int flags, struct sockaddr *from, socklen_t *fromlen); Sintaksa izgleda komplikovano, ali u sustini nije ;) recv() i recvfrom() su relativno slicne funkcije, i obe se koriste za prihvatanje podataka. Razlika je sto se recv() koristi za sockete koji su connection-oriented, a recvfrom() moze da se koristi u svakom slucaju. Prvi parametar predstavlja socket iz kojeg nam dolazi mrezni saobracaj. Drugi parametar je pokazivac na buffer, odnosno na mesto u koje ce biti zapisani svi procitani podaci.Kao treci parametar se prosledjuje velicina navedenog buffera, odnosno memorijskog prostora alociranog za smestaj podataka. U cetvrtom parametru se navodi kako ce se podaci iz socketa iscitavati, kako ce sistem reagovati ako nam dolazi veca kolicina podataka od ocekivane... Peti parametar je struktura sockaddr koja nam kazuje odakle se ocekuje poruka, a sesti parametar predstavlja duzinu strukture sockaddr. Poslednja tri parametra nam nisu toliko bitni za pravilno funkcionisanje sniffera koji nameravamo da napravimo. Kome trebaju detalji, man recvfrom. Jedna vazna napomena u vezi sa recvfrom(): pri prvom pozivanju recvfrom(), bitno je da sesti parametar bude postavljen na neku memorijsku lokaciju sa inicijalnom vrednoscu sizeof(struktura_petog_parametra), a kasnije se taj parametar menja u zavisnosti od vracene vrednosti. Znaci potrebno je da pri programiranju napisemo nesto tipa: received = recvfrom(........, &received); Ali, jos jednom da napomenem, bitno je na samom pocetku podesiti vrednost received na velicinu petog prosledjenog parametra. Shvaticete ovo kasnije kroz kod. Ovo je primer blokirajuce I/O funkcije, odnosno funkcije koja ce da stoji i da ceka sve dok nesto ne dodje. Takodje moguce je namestiti i neblokirajucu recvfrom() funkciju, ali nam to trenutno nije od velikog znacaja. ---< 1.4 htons(), htonl(), ntohs(), ntohl() uint16_t htons(uint16_t hostshort) uint32_t htonl(uint32_t hostlong) uint16_t ntohs(uint16_t netshort) uint32_t ntohl(uint32_t netlong) Funkcije za prevodjenje adresa iz network byte redosleda u host byte redosled, i obrnuto. Na 80x86 arhitekturi pri host byte redosledu, bajt najmanje vaznosti, odnosno vrednosti je na prvom mestu, dok je pri network byte redosledu bajt najvece vrednosti na prvom mestu. //////////////////////////////////////////////////////////////////////////// --==<[ 2. Struktura jednog kompletnog paketa \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ Svaki paket koji putuje kroz mrezu se sastoji od niza headera i podataka koje nosi. Jedan tipican paket koji putuje od jednog do drugog hosta sadrzi u sebi ethernet header, ip header i jos jedan header u zavisnosti od tipa paketa (tcp header, udp header, icmp header...). Ovako bi izgledao jedan tipican paket koji mi hvatamo: --------------------------------------------------------------------- |> eth header <|> ip header <|> {tcp, udp, icmp} header <|> podaci <| --------------------------------------------------------------------- ------------------------------ naziv : ethernet header velicina : 14 bajtova strukutura : bajt | naziv +------+--------------------------- | 1-6 | MAC adresa onog ko salje | 7-12 | MAC adresa onog ko prima |13-14 | o kom se protokolu radi +------+--------------------------- napomena : Poslednja dva bajta 13. i 14. su nam uglavnom 08 00 (hex) jer uglavnom svi radimo sa IPv4 (PF_INET) protokolom. Neke od mogucih vrednosti su: 08 00 IPv4 86 DD IPv6 08 06 ARP (Adress Resolution Protocol) ... ------------------------------- naziv : ip header biblioteka: #include , struct iphdr; velicina : 20 bajtova (tipicno) struktura : bajt | naziv +----------------------------------------------------------------- | __u8 ihl:4; // velicina ip headera | __u8 version:4; // 0100 za IPv4, 0110 za IPv6 (ver. prot.) | __u8 tos; // type of service | __u16 tot_len; // ukupna duzina ip datagrama | __u16 id; // id broj fragmenta ip datagrama | __u16 frag_off; // koristi se za fragmentaciju (dod. bajt) | __u8 ttl; // Time To Live | __u8 protocol; // koji protokol viseg nivoa nosi ovaj paket | __u16 check; // checksum (za integritet paketa) | __u32 saddr; // source address (adresa posiljaoca) | __u32 daddr; // destination address (adresa primaoca) +----------------------------------------------------------------- napomena : Za nas su ovde veoma vazna polja 'protocol', 'saddr' i 'daddr'. Polje 'protocol' nam govori, u stvari, o kojem se paketu radi, tj. da li je to TCP, UDP, ICMP, IGMP... Prema tome, ovo polje moze da ima sledece vrednosti: - TCP : 06 - UDP : 11 - ICMP : 01 - ... Strukture headera paketa viseg nivoa od eth&ip dela moramo imati takodje u vidu, jer je to ono zbog cega smo ovde ;) ustvari, nama od podataka su jos samo bitno brojevi portova izmedju kojih se vodi komunikacija ili tacno znacenje poslate poruke (ako se radi o kontrolnim ICMP i slicnim porukama). Ti podaci se nalaze unutar sledeceg dela paketa, koji se nalazi posle eth i ip headera. Znaci, ako hocemo npr. da pratimo saobracaj tcp paketa, onda moramo poznavati strukturu tcp headera. U sustini najbitnije je znati duzinu headera, jer onda znamo da posle headera dolazi stvarni podaci, tj. oni podaci zbog kojih svi ti headeri postoje. Sada cu prikazati strukturu udp headera, posto je on najmanji: [netinet/udp.h] struct udphdr { __u16 source; // port sa kojeg posiljalac salje podatke __u16 dest; // port na koji ce primalac da primi podatke __u16 len; // ukupna duzina podataka koji se salju __u16 check; // checksum udp paketa }; Kao sto vidimo, velicina udp headera je 2+2+2+2=8 bajtova, sto znaci da bi dosli do pravih podataka, moramo se pomeriti za jos 8 bajtova unapred. Sve ovo ce vam biti mnogo jasnije kada predjemo na konstrukciju programa. //////////////////////////////////////////////////////////////////////////// --==<[ 3. Osnovni elementi konstrukcije \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ Kada pokrenete jedan sniffer, on prolazi kroz vise faza pre nego sto pocne da vam izbacuje podatke na ekran. Napravio sam neku moju podelu po slobodnom izboru, i nadam se da ce vam ona dobro-doci da stvorite sliku o tome sta sniffer zapravo radi. ---< I faza: otvaranje socketa U ovoj fazi je bitno da otvorimo jedan socket preko kojeg cemo primati podatke. Ali pre toga bi trebalo da ste odlucili koji tip socketa koristite i koji skup protokola zelite snimati. Ja vam preporucujem PF_PACKET u kombinaciji sa SOCK_RAW, jer vam je jedan od najnizih mogucih nivoa za hvatanje paketa. sock = socket(PF_PACKET, SOCK_RAW, 0); Pored otvaranja socketa, bilo bi pozeljno proveriti da li se nije dogodila neka greska prilikom otvaranja socketa. Za to koristimo i funkciju perror(). Znaci ako socket() vrati -1, odnosno bilo sta manje od 0, onda pozivamo perror("socket") da bi bili dobro obavesteni o radu naseg sniffera. ---< II faza: hvatanje paketa u bafer Posto smo usmerili nase oci, odnosno socket na mrezni saobracaj koji zelimo da posmatramo, onda je vreme za funkciju recvfrom(), kako bi pomocu nje pohvatali sav saobracaj u buffer. Kao sto smo rekli pri opisu sintakse recvfrom(), potrebno je prvo inicijalizovati vrednost promenljive (objekta) u koji ce recvfrom() da vrati neku vrednost (return). received = sizeof(from); Sad kad smo sve spremili, potrebno je recvfrom() ubaciti u neku petlju jer smo verovatno tu da bi primili vise paketa, a ne samo jedan. while(1) { received = recvfrom(sock, buffer, sizeof(buffer), 0, (struct sockaddr *)(&from), &received) ... } Napomena: bafer za prihvatanje podataka (u ovom slucaju buffer) ne sme biti premali. U zavisnosti od flag parametra (4-ti parametar), sistem ce odluciti da li da odbije podatak, ili da ga prenosi deo po deo u bafer, tzv. fragmentacija paketa. Zato slobodno stavite veci bafer, sta vam znaci npr. 64k memorije??? ---< III faza: parsiranje paketa Podatke koje smo prihvatili u bafer mozemo prikazivati u raw modu, tj. bas onako kako ih primamo. Medjutim, u tom slucaju ne bi imali bas neke koristi od sniffera. Zato je bitno odvojiti header-e od stvarnih podataka. Rekli smo da se na pocetku paketa nalazi eth header, pa ip header, pa onda jos jedan header u zavisnosti od tipa konekcije i sl. Za pocetak cemo da izdvojimo eth header. ip = (struct iphdr *) (buffer + sizeof(struct ethhdr)); Struktura iphdr nam pomaze da identifikujemo polja ip headera u paketu koji smo pokupili. Za sada nam je najvaznije polje 'protocol' da bi mogli pravilno da rasporedimo sve pakete, odnosno da mozemo da razlikujemo tcp, udp, icmp i druge pakete. Znaci, ako hocemo tcp pakete, onda prihvatamo samo one koji u polju ip->protocol imaju vrednost 6. A ako nam treba vise azlicitih vrsta paketa, onda je najbolje da se za to koristi switch(). switch (ip->protocol) { case 6: {tcp protokol} break; case 11: {udp protokol} break; default: {unknown protokol} } ---< IV faza: prikaz pohvatanih paketa Naravno, kada napokon dodjemo do zeljenih paketa, jedina preostala stvar u vezi njih jeste prikaz istih. Potrebno je odabrati podatke koje cemo da logujemo i koji su bitni za nas. Prva stvar koja je bitna je adresa sa koje je paket poslat i adresa kojoj je paket namenjen. Te informacije se nalaze u poljima saddr i daddr, kao sto smo prikazali u poglavlju 0x02 o strukturi paketa. Posto je taj podatak napisan po network byte redosledu, onda moramo koristiti i funkciju inet_ntoa kako bi preveli taj podatak u string. Sintaksa: char * inet_ntoa(struct in_addr in); Kao sto vidimo ocekuje se objekat tipa in_addr, a ip broj se cuva u objektu tipa iphdr. Zato je bitno da taj ip broj prvo prebacimo u neki objekat tipa in_addr, pa onda da pozovemo inet_ntoa(): struct in_addr ipsource, ipdest; ipsource.s_addr = ip->saddr; ipdest.s_addr = ip->daddr; Tek sada mozemo pozvati inet_ntoa(). Primer pozivanja inet_ntoa() printf ("Source IP: %s\n", inet_ntoa(ipsource)); printf ("Destination IP: %s\n", inet_ntoa(ipdest)); Posto je mene licno nervirao ovaj 'casting', napisao sam funkciju koja odradjuje sama to: char * my_ntoa(unsigned long adress) { char temp[16]; char *ret; int a, b, c, d; a = adress / 16777216; adress = adress % 16777216; b = adress / 65536; adress = adress % 65536; c = adress / 256; adress = adress % 256; d = adress; sprintf (temp, "%d.%d.%d.%d", d, c, b, a); ret = malloc(strlen(temp)); memset (ret, '\0', sizeof (temp)); strcpy (ret, temp); return ret; } Pomocu ove funkcije mozete odmah pozvati my_ntoa(ip->saddr) ili daddr. Inace, kao sto se vidi, isao sam d, c, b, a. To je zato sto je to network byte redosled, koji se inace koristi na internetu. Inverzna funkcija ovoj bi bila: adress = a*256^3 + b*256^2 + c*256 + d. Otisao sam mnogo u sirinu ovog dela poglavlja, ali od viska ne boli glava, valjda. Posle utvrdjivanja tipa paketa viseg nivoa u odnosu na eth i ip, bitno je poznavati svojstva i strukturu predstojeceg dela paketa. Najvaznije od svega jeste velicina headera, da bi znali gde se nalaze stvarni podaci koje nosi paket. Ako smo utvrdili da se radi o TCP paketu, onda je za nas bitan port racunara sa kojeg je paket poslat i port primaoca paketa, odnosno racunara kojem je paket upucen. Ti podaci se nalaze u poljima 'source' i 'dest'. Vise informacija o strukturi tcp paketa mozete naci u . TCP paket cemo dobiti tako sto cemo se pomeriti za velicinu eth i ip headera: tcp = (struct tcphdr *)(buffer + sizeof(struct ethhdr) + sizeof(struct iphdr)); Podaci koje smo trazili jesu portovi. Posto su i oni smesteni po network byte redosledu, potrebno je i njih prebaciti u host byte redosled pomocu funkcije ntohs(): printf ("Source Port: %d", ntohs(tcp->source)); printf ("Destination Port: %d", ntohs(tcp->dest)); //////////////////////////////////////////////////////////////////////////// --==<[ 4. Primitivna verzija sniffera \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ #include #include #include #include #include #include #include #include #include int main () { struct sockaddr_in from; struct ethhdr *eth; struct iphdr *ip; struct tcphdr *tcp; struct udphdr *udp; struct in_addr srchost; struct in_addr desthost; char buffer[2048]; int received; int sock; // otvaramo socket za prihvatanje mreznog saobracaja sock = socket (PF_PACKET, SOCK_RAW, htons(ETHERTYPE_IP)); // u slucaju greske pri pozivanju socket() if (sock < 0) { perror ("socket"); exit (-1); } // inicijalizujemo received received = sizeof (from); // ulazimo u petlju za hvatanje paketa while (1) { // cistimo bafer memset (buffer, '\0', sizeof (buffer)); // cekamo neki paket i smestamo ga u bafer received = recvfrom (sock, buffer, sizeof (buffer), 0, (struct sockaddr *)(&from), &received); // vrsimo prepoznavanje ip headera u baferu ip = (struct iphdr *) (buffer + sizeof(struct ethhdr)); // popunjavamo dve in_addr strukture da bi mogli da pretvorimo ip adrese // u string pomocu inet_ntoa() funkcije srchost.s_addr = ip->saddr; desthost.s_addr = ip->daddr; // razvrstavanje paketa switch (ip->protocol) { case 6 : // prepoznajemo tcp header u preostalom baferu tcp = (struct tcphdr *) (buffer + sizeof(struct ethhdr) + sizeof(struct iphdr)); // prikaz uhvacenih podataka na ekran printf ("TCP:> [%s:%d]>[%s:%d]\n", inet_ntoa(srchost), ntohs(tcp->source), inet_ntoa(desthost), ntohs(tcp->dest)); break; case 17: // prepoznajemo udp header u preostalom baferu udp = (struct udphdr *) (buffer + sizeof(struct ethhdr) + sizeof(struct iphdr)); // prikaz uhvacenih podataka na ekran printf ("UDP:> [%s:%d]>[%s:%d]\n", inet_ntoa(srchost), ntohs(udp->source), inet_ntoa(desthost), ntohs(udp->dest)); break; default : // u slucaju da se radi o protokolu koji nam ne treba printf ("UNKNOWN TYPE OF PACKET\n"); } } exit(0); } //////////////////////////////////////////////////////////////////////////// --==<[ 5. Nadogradnja \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ U poglavlju 0x04 je prikazan izvorni kod najjednostavnijeg sniffera sa najskromnijim mogucnostima. Tu sada ima mnogo mesta za poboljsanja. U ovom poglavlju cemo obraditi dosta "features"-a koje mozete dodati snifferu kroz one cetiri navedene faze. ---< I faza : filtriranje odredjenog interfejsa Verovatno vam je potrebno da sniffate pakete sa odredjenog interfejsa, a ne sa svih... Na primer, ako su vam bitni samo podaci u lokalnoj mrezi, i to samo oni koji dolaze sa npr. eth1 interfejsa... Posto jedan red koda vredi vise od hiljadu reci ;), predjimo na kod: #include #include #include ... struct ifreq ifr; struct sockaddrll sockll; ... // pronadji interfejs memset (&ifr, '\0', sizeof(ifr))); strncpy(ifr.ifr_name, INTERFACE, sizeof(ifr.ifr_name))); if (ioctl(sock, SIOCGIFINDEX, &ifr) < 0) { perror ("ioctl(SIOCGIFINDEX)"); exit (-1); } // popuni sockll(sockaddrll) strukturu memset (&sockll, '\0', sizeof(sockll)); sockll.sll_family = PF_PACKET; sockll.sll_ifindex = ifr.ifr_ifindex; sockll.sll_protocol = htons(ETH_P_ALL); // binduj socket na INTERFACE if (bind(sock, (struct sockaddr *) &sockll, sizeof(sockll)) < 0) { perror ("bind"); exit (-1); } ---< I faza : ukljucivanje promiscuous moda Mmmmm, strcmp("promisc mod", "mothfucka mod") == 0... Promiscuous mod je nacin rada nekog interfejsa takav da ne odbacuje one pakete koji mu ne pripadaju... Najbolji primer toga je neki LAN gde hub salje sve pakete svima a na vama je odbacujete one pakete koji nisu vama upuceni. Takva je, inace, situacija po defaultu... Ali zato kada bi ukljucili ovaj mod, onda bi mogli da sniffamo celu mrezu. Meni to bas dobro zvuci... Pitanje: Kako ukljuciti 'promiscuous mode'? Postoje dva nacina: prvi nacin je da otkucate iz konzole: backspace@bitbyterz# ifconfig promisc Sada smo aktivirali promisc mod na interfejsu. Medjutim, ovo i nije bas zgodno, pogotovo zato sto vas "oci zla" uvek posmatraju ;) Ovo je, naravno moguce uraditi i iz samog sniffera, ubacivanjem sledeceg koda: #include #include ... struct ifreq ifr; ... if (ioctl(sock, SIOCGIFFLAGS, &ifr) < 0) { perror ("ioctl(SIOCGIFFLAGS)"); exit (-1); } ifr.ifr_flags |= IFF_PROMISC; if (ioctl(sock, SIOCSIFFLAGS, &ifr) < 0) { perror ("ioctl(SIOCSIFFLAGS)"); exit (-1); } ---< I faza : signal handler Pri radu sniffera moze da se desi da ostane "repova" prilikom gasenja istog. Na primer, niste zatvorili socket ili niste zatvorili fajl u koji ste upisivali sniffovane podatke, ili niste iskljucili promisc mod koji ste bili prethodno ukljucili... To sve treba uraditi da bi sakrili tragove iza vas... Uostalom, valjda uvek iza sebe treba pocistiti ;) Da bi sve ovo mogli da odradimo, moramo koristiti signal handler koji ce da obradjuje signale koje nas program primi. #include ... signal (SIGSEGV, func); signal (SIGTERM, func); signal (SIGQUIT, func); ... Umetanjem ovih par redova u sniffer, napravili smo signal handler koji ce pri SIGSEGV, SIGTERM i SIGQUIT signalima da pozove funkciju func(). Sada nam ostaje jos da napisemu tu funkciju: void func() { // ovde treba dodati funkcije za zatvaranje svega sto ste otvorili ;) // iskljucivanje promisc moda if (ioctl(sock, SIOCGIFFLAGS, &ifr) < 0) exit(-1); if (ifr.ifr_flags & IFF_PROMISC) ifr.ifr_flags ^= IFF_PROMISC; if (ioctl(sock, SIOCSIFFLAGS, &ifr) < 0) exit(-1); // zatvaramo sock close (sock); // izlaz exit (0); } ---< II faza : stavljanje procesa u background sa fork() fork()-ovanjem mozemo staviti neki proces ta se odvija u pozadini... ako bi sniffer trebao da bude sakriven na sistemu, onda vam je fork() neizbezan. Mada, slican efekat moze da se dobije tako sto cemo sniffer pokrenuti sa: backspace@bitbyterz# ./sniffer & [1] 7262 backspace@bitbyterz# Sada je upravo pokrenut program sniffer i dodeljen mu je pid 7262 i sada korisnik moze nastaviti i dalje nesmetano sa radom. Da bi stavili u pozadinu neki proces ili neku funkciju, potrebno je stavimo deo koda sniffera koji sluzi za hvatanje paketa u neku posebnu funkciju, radi preglednosti koda. Mogli bi i bez toga, ali bi bilo mnogo gadno za pregled. Znaci napravicemo funkciju catchpackets() koja ce da se bavi samo hvatanjem paketa, sto i jeste sustina sniffera, i onda cemo dodati ovaj deo koda: if (!fork()) catchpackets(); Mada mogli bi i da ostavimo mogucnost da moze i da se izabere da li da proces bude u backgrounu ili ne. To mozete uraditi na vise nacina, da ne nabrajam. ---< II faza : detekcija eth headera Prilikom koriscenja prvog source-a, odnosno primitivnog sniffera, primeticete da on lepo sniffuje, ali samo na loopback (lo) i eth interfejsu, dok na ppp ne radi bas najbolje, odnosno ne prepoznaje dobro sve headere. To je zato sto u uhvacenim paketima nema eth headera kod ppp interfejsa. Zato treba, u odnosu na izabrani interfejs, da uzimamo u obzir duzinu eth headera ili ne. Znaci, ako se radi o 'lo' ili 'eth' interfejsu onda cemo ethlen da postavimo na sizeof(struct ethhdr), a ako nije onda ethlen=0. if (INTERFACE == "lo" || !strcmp("eth", INTERFACE)) ethlen = sizeof(struct ethhdr); else ethlen = 0; ---< II faza : filtriranje dupliranih paketa na loopback interfejsu Ovde nema sta narocito da se kaze, sem toga da se paketi koji dolaze iz loopback interfejsa dupliraju. Da bi sprecili prikaz "duplih" paketa, treba da ispitamo da li se radi o odlazecem paketu (outgoing), jer pomalo je i glupo govoriti o outgoing paketima na loopback interfejsu: if (from.sll_pkttype == PACKET_OUTGOING && !strcmp(INTERFACE, "lo")) { continue; } ---< III faza : prepoznavanje icmp paketa Posto smo ugradili prepoznavanje i prikaz dve najkoriscenije vrste paketa (tcp i udp), mogli bi i da dodamo icmp pakete (internet control message protocol). Dva najvaznija parametra icmp paketa su tip i kod. Prema tome mi mozemo da zakljucimo koja je svrha neke ICMP kontrolne poruke, kao sto su ICMP_ECHO, ICMP_ECHOREPLY, ICMP_REDIRECT... #include ... struct icmphdr *icmp; ... switch (ip->protocol) { ... case 1: icmp = (struct icmphdr *) (buffer + ethlen + sizeof(struct iphdr)); printf ("ICMP:> [%s>-<%s] --> TYPE: %d, CODE: %d\n", my_ntoa(ip->saddr), my_ntoa(ip->daddr), icmp->type, icmp->code); break; } ---< III faza : uzimanje detalja od paketa nepoznatog tipa Desice nam se ponekad da nam prodje paket nekog tipa koji nas sniffer ne prepoznaje. U tom slucaju bi bilo najbolje da uhvatimo koji je to protokol, odnosno njegov identifikacioni broj, kako bi znali koju strukturu paketa treba da u ugradimo u nas sniffer da bi mogli da uzimamo informacije i iz te nove vrste. Ono sto odavde mozemo dobiti, a da je od znacaja, jeste ip->protocol. Sem toga, mozemo uzeti i ostale podatke koje nosi paket u raw modu, ali to cu tek opisati u IV fazi. ... switch (ip->protocol) { ... default: printf ("UNKNOWN:> [%s>-<%s] --> PROTOCOL: %d\n", my_ntoa(ip->saddr), my_ntoa(ip->daddr), ip->protocol); } ---< IV faza : prikaz "pravih" podataka Pravi podaci, odnosno podaci zbog koji paket uopste postoji nalaze se na kraju svih headera. Znaci potrebno je znati prvo koji se headeri nalaze pre podataka i koja je njihova velicina, da bi znali koja je pozicija podataka u buffer-u. data_len = ntohs(ip->tot_len) - (zbir_svih_headera_od_ip_headera_pa_nadalje); buff = (buffer + ethlen + zbir_svih_headera_od_ip_headera_pa_nadalje); Sada cu kroz primer da odstampam podatke koje nosi tcp paket: data_len = ntohs(ip->tot_len) - sizeof(struct iphdr) - sizeof(struct tcphdr); buff = (buffer + ethlen + sizeof(struct iphdr) + sizeof(struct tcphdr); for (i=0; i ... struct tm *lt; time_t tt; ... // odmah posle prijema paketa tt = time(0) lt = localtime(&tt); ... // lt->tm_mday, lt->tm_mon, lt->tm_year, lt->tm_hour, lt->tm_min, lt->tm_sec printf ("%d", lt->...); ---< Mesto za dalje improvizacije Pored svega opisanog i svih ovih dodataka, uvek ima mesta za dalje napredovanje. Na primer, direktno sto se tice sniffera koji je napisan u okviru ovog texta: - obojiti pojedine segmente pri prikazu uhvacenih podataka radi lakseg raspoznavanja i snalazenja - koriscenje fajlova za pamcenje i sortiranje uhvacenih podataka umesto stdout-a koji je ovde koriscen - prikaz MAC adrese, koja se nalazi u eth headeru, prvih 12 bajtova (1-6 source mac, 7-12 destination mac) - arp spoofing (da bi mogli da hvatate podatke uprkos switch-u) - podrska nekim drugim vrstama protokola - ... //////////////////////////////////////////////////////////////////////////// --==<[ 6. Konacna verzija sniffera \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ begin 644 bbsniff.c M+RH@(&)B2!"84-K4W!!8T4*("`@($)I=$)Y=&5R>B!, M86)S+"`R,#`U"B`@("!H='1P.B\O=W=W+F)I=&)Y=&5R>BYC;RYS<@H*("`@ M($-O;7!I;&4Z(&=C8R!B8G-N:69F+F,@+5=A;&P@+4\S("UO(&)B7,O='EP M97,N:#X*(VEN8VQU9&4@/'-Y/2!)1D9?4%)/34E30SL*("`*("`@(&EF("AI;V-T;"AS;V-K M+"!324]#4TE&1DQ!1U,L("9I9G(I(#P@,"D@"B`@("`@(&5X:70@*"TQ*3L* M("!]"B`@"B`@+R\@>F%T=F]R:2!S;V-K970*("!C;&]S92`HPH@('-TF5O9B`H9G)O;2D[("`*"B`@+R\@=6QA>FEM;R!U('!E=&QJ=2!Z M82!H=F%T86YJ92!P86ME=&$*("!W:&EL92`H,2D@>PH@("`@+R\@8VES=&EM M;R!B869E<@H@("`@;65MF5O9B`H8G5F M9F5R*2D["@H@("`@+R\@8V5K86UO(&YE:VD@<&%K970@:2!S;65S=&%M;R!G M82!U(&)A9F5R"B`@("!R96-E:79E9"`](')E8W9FF5O9B`H8G5F9F5R*2P@,"P@"B`@("`@("`@("`@("`@("AS=')U M8W0@G5J96UO('9R96UE(&AV871A;FIA('!A:V5T80H@("`@<')I;G1F("@B)3`R M9"TE,#)D+24T9"`E,#)D.B4P,F0Z)3`R9"`B+"`@;'0M/G1M7VUD87DL(&QT M+3YT;5]M;VX@*R`Q+"`*("`@("`@("`@("`@;'0M/G1M7WEE87(@*R`Q.3`P M+"!L="T^=&U?:&]UFYA=F%N:F4@:7`@:&5A9&5R82!U(&)A9F5R=0H@ M("`@:7`@/2`HG9RPH@("`@("`@("`@8V%S92!40U!?4%)/5$]#3TPZ("\O('!R M97!O>FYA:F5M;R!T8W`@:&5A9&5R('4@<')E;W-T86QO;2!B869EB!U:'9A8V5N:6@@<&]D871A:V$@;F$@96MR M86X*"0D@("!P5]N=&]A*&EP+3YS861DF5O9BAS=')U8W0@:7!H9'(I(`H)"2`@("`@("`@ M("`@("`@+2!S:7IE;V8HF5O9BAS=')U8W0@:7!H9'(I(`H)"2`@ M("`@("`@("`K('-I>F5O9BAS=')U8W0@=&-P:&1R*2D["@D)("`@("!F;W(@ M*&D],#L@:3QD871A7VQE;CL@:2LK("D@>PH)"2`@("`@("!I9B`H2$58*2`* M"0D@("`@("`@("!PF5O9BAS=')U8W0@:7!H9'(I*3L*"2`@("`@("`@("`@+R\@<')I:V%Z('5H M=F%C96YI:"!P;V1A=&%K82!N82!E:W)A;@H)"2`@('!R:6YT9B`H(E5$4#H^ M(%LE5]N=&]A*&EP+3YD861DB`B<')A=FEH(B!P;V1A=&%K80D)("`@ M"@D)("`@:68@*$1!5$$I('L*"0D@("`@('!R:6YT9B`H(CHZ.B`B*3L*"0D@ M("`@(&1A=&%?;&5N(#T@;G1O:',H:7`M/G1O=%]L96XI("T@F5O9BAS=')U8W0@ M=61P:&1R*3L*"0D@("`@(&)U9F8@/2`H8G5F9F5R("L@971H;&5N("L@5]N=&]A*&EP+3YS861DPH)"2`@("`@<')I;G1F("@B.CHZ("(I M.PH)"2`@("`@9&%T85]L96X@/2!N=&]HPH)"2`@("`@("!I9B`H2$58*2`*"0D@("`@("`@("!P5]N=&]A*&EP+3YS861D5]N=&]A*&EP+3YD861DF5O9BAS M=')U8W0@:7!H9'(I.PH)"2`@("`@8G5F9B`]("AB=69F97(@*R!E=&AL96X@ M*R!S:7IE;V8HPH)"2`@("`@("!I9B`H2$58*2`*"0D@("`@("`@ M("!PPH@('-T&ET:6YG*3L*("!S:6=N86P@*%-) M1U%5250L(&5X:71I;F&ET:6YG*3L*"@H@("\O(&]T=F%R86UO('-O8VME="!Z82!PFEV86YJ=2!S;V-K970H*0H@(&EF M("AS;V-K(#P@,"D@>R`@("`@("`@("`@("`@("`@("`@("`@("`@"B`@("!P M97)R;W(@*")S;V-K970B*3L@("`@("`@("`@("`@("`@("`@("`*("`@(&5X M:70@*"TQ*3L@"B`@?0H*("`O+R!P2AI9G(N:69R7VYA;64L($E.5$521D%#12P@PH@("`@<&5RPH@("`@<&5RF$@;F%V M961E;FD@:6YT97)F96IS"B`@:68@*%!23TU)4T,I('L*("`@(&EF("AI;V-T M;"AS;V-K+"!324]#1TE&1DQ!1U,L("9I9G(I(#P@,"D@>PH@("`@("!P97)R M;W(@*")I;V-T;"A324]#1TE&1DQ!1U,I(BD["B`@("`@(&5X:70@*"TQ*3L* M("`@('T*("`*("`@(&EF&ET("@M,2D["B`@("!]"B`@?0H*("`O+R!D82!L:2!D82!F;W)K*"DM M=6IE('!R;V-E, npr: backspace@bitbyterz# ifconfig eth0 eth0 Link encap:Ethernet HWaddr 00:11:09:94:A2:3D inet addr:192.168.0.1 Bcast:192.168.0.255 Mask:255.255.255.0 UP BROADCAST PROMISC MULTICAST MTU:1500 Metric:1 RX packets:0 errors:0 dropped:0 overruns:0 frame:0 TX packets:4 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:1000 RX bytes:0 (0.0 b) TX bytes:240 (240.0 b) Interrupt:5 Base address:0xef00 U trecem redu vidimo da pise PROMISC, sto znaci da se ovaj interfejs nalazi u promiscuous modu, sto znaci da imamo verovatno pokrenut neki sniffer na sistemu. Ovaj nacin detekcije promisc moda funkcionise na vecini unixoidnih operativnih sistema. Kod bsd-like sistema, sve ovo moze biti zamenjeno samo jednom komandom: ifconfig -a Bas zbog ovog nacina detekcije sniffera, vecina napadaca ce pokusati da vam zameni vas ifconfig sa "patchovanim" ifconfig-om koji nece prikazati da li je neki interfejs u promisc modu. Zato je, takodje, bitno da proverite checksum vaseg ifconfig-a. Detektovanje sniffera koji nemaju ukljucen promisc je teze. Medjutim, snifferi koji loguju sav mrezni saobracaj u neki fajl ili na bilo koji drugi nacin, mogu lako biti otkriveni, pogotovo na racunarima kroz koje prolazi velika kolicina podataka. Tada ce sniffer verovatno napraviti veliki load na takvom racunaru. Jedan od efikasnijih nacina sprecavanja prisluskivanja lokalnih mreza jeste koriscenje aktivnih hub-ova (switch-eva), sto znaci da vam treba bolja i kvalitetnija oprema (znaci li vam nesto ime 3COM???). Aktivni hub-ovi rade na nesto visem OSI nivou (za razliku od obicnih hubova koji samo kopiraju i salju podatke), i oni salju ciljnom racunaru samo one podatke koji su njemu namenjeni. Obicni hub-ovi salju sve podatke svima, tako da ostavljaju racunaru da sam odbacuje tudje pakete. Ni ovaj nacin nije bas 100% savrsen, jer tu dolazi jedna tehnika koja se zove arp spoofing, o kojoj cu mozda pisati sledeci put. Najsigurniji nacin zastite jeste enkripcija, zato sto podaci koji su enkriptovani ne znace nesto mnogo napadacu, jer je uglavnom veoma tesko naci kljuc po kojem je nesto kodovano (kriptovano). Danas se prave mnogi servisi (daemoni) bazirani na SSL (Secure Socket Layers), koji automatski kriptuje podatke, dok header i dalje ostaje citljiv. Koriscenjem one-time password tehnologije mozemo skroz onesposobiti sve sniffere, jer kljucevi koje napadac uhvati vaze samo u jednom prolazu, a vec u sledecem password se menja. Jos jedna stvar koja je moguca, a to je da vasa mrezna karta ne moze da bude u promisc modu. Molite Boga da je tako, ali je mala verovatnoca. HP je proizvodio dosta ovakvih mreznih karti. Uglavnom sve mrezne kartice koje su bazirane na TROPIC chipsetu ne mogu da budu u promisc modu. //////////////////////////////////////////////////////////////////////////// --==<[ 8. Literatura \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ (1) man pages (socket, htonl, netdevice, raw, perror, packet...) (2) RFC (dosta komada) (3) Sniffers FAQ, How to detect sniffers running Christopher Klaus of Internet Security Systems Inc. (4) TCP/IP Illustrated Vol. 1-3 Richard Stevens (5) Beej's Guide to Network Programming Beeeej the great //////////////////////////////////////////////////////////////////////////// --==<[ 9. Odvod \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ Podaci su danas skroz nesigurni, tako da bi verovatno najsigurniji nacin za prenos podataka bio da vi licno odnesete nekome cd sa podacima, pritom cuvajuci cd da se ne izgrebe, a ujedno cuvajuci se od "zaseda" ;) Uostalom zasto uopste brinuti o podacima jer podaci i treba da budu slobodni da moze svako da ih procita... ko nesto krije, verovatno ne krije nista dobro... Ako ima prezivelih nakon citanja ovog teksta, i ako se medju prezivelima krije neki znatizeljnik, preporucio bi mu dalje da se baci na arp spoofing, ili eventualno na pisanje nekih tool-ova za enkripciju (Sizifov posao). greetz to: d0lphin, **W**, De1Rekt0n, BORG, SSnaVe i svim ostalim clanovima grupa sa kojima je bilo divno druziti se (Serbian Security Team i bSecurity Team)...