A port scanning egy olyan technika, aminek segítségével feltérképezhető, hogy egy kiszemelt gépen milyen hálózati szolgáltatások futnak, onnan, hogy mely portok vannak nyitva. Ilyen általában akkor történik, ha valaki támadási pontot keres egy szerveren, hiszen mint tudjuk, minden elindított szolgáltatás azon kívül, hogy az átlag felhasználó életét megkönnyíti, kiskapukat is jelent az ügyeskedők számára. A port scanning bizonyos fajtái könnyen detektálhatóak, illetve kiszűrhetőek az "áldozat" oldalán. Mindenképpen érzékelni fogja, hogy egyszerre számos portra történt kapcsolódási próbálkozás, még olyanokra is, melyeket egyébként nem is használ egy szolgáltatás sem. Maga a port scanning még nem tekinthető támadási kísérletnek, de azt valószíűleg megelőzi.
 

Számos változata van, mindegyik fura névvel:

  • Vanilla

  • Ez a módszer a legegyszerűbb, és leggyorsabb. Egyszerre mind a 65536 porton megpróbál a program connect-elni.
     
  • Strobe

  • Hasonló az előzőhöz, avval a különbséggel, hogy csak azoknak a szolgáltatásoknak a portjaira próbál kapcsolódni, amiket a támadó ki is tud használni.
     
  • Stealth scan

  • Az előző módszerek könnyen felfedezhetőek. Ha kapcsolódik valaki az adott portra, nem küld semmi adatot, csak bezárja azt, akkor a szolgáltatás el fogja loggolni ezt a történést. Az ún. Stealth scan technikák nem építenek ki kapcsolatot, csak úgy tesznek, mintha szeretnének, csak egy TCP csomagot küldenek. Több típusa van attól függően, hogy ez milyen csomag. Lehet SYN scan, FIN scan, NULL scan.
     
  • FTP bounce scan

  • Az FTP szolgáltatás egyik érdekessége, hogy "proxy" funkciója is van. Egy ilyen szerveren keresztül nyúlva a scannel-t gép nem fogja látni a támadó IP címét. A legtöbb FTP szerveren már le van tiltva ez a lehetőség.
     
  • Fragmented packets

  • A stealth scan-t lehet vele kombinálni. A TCP csomagot nem egyben, hanem darabonként küldi el a program. Ezzel néhány csomag filterező tűzfalat át lehet ejteni, mivel nem látják az egész IP csomagot, nem tudják azonosítani egy tűzfal szabállyal sem, és átengedik. Az újabb linux kernelek alapértelmezés szerint megvárják az összes töredéket, és összerakva vizsgálják (CONFIG _IP_ALWAYS _DEFRAG opció a kernelfordításkor), így erre immunisak.
     
  • UDP scan

  • Számos szolgáltatás működik UDP-n is. Az UDP egyszerűbb protokoll, de néha nehezebb scannel-ni, mivel itt a nyitott port-ok nem szükségszerűen küldenek választ.
     
  • Sweep scan

  • Egyszerre több gép ugyanazon portjára próbálunk kapcsolódni. Az előző technikák valamelyikével
     
  • ICMP scan

  • Egy adott IP tartomány gépeit megpingeljük, hogy egyáltalán van-e ott valaki. Csak azt lehet megállapítani, hogy az adott címen van-e gép. Nem tartozik szorosan a port scanning-hez, hiszen az ICMP-nek nincsenek is portjai.

Nézzük a legegyszerűbb módszert.

Ez úgy működik, hogy mind a 65536 port-ra meghívjuk a connect függvényt. Ez persze igaziból ennél bonyolultabb. Az összes portra egyszerre nem tudunk kapcsolódni, hiszen ez 65536 nyitott socket-et jelentene. A rendszer nem fogja tudni ezt rendelkezésünkre bocsátani. Tehát egyszerre csak korlátozott számú port-ot tudunk vizsgálni.

A kapcsolódás a már ismert módon történik. Azzal a különbséggel, hogy a gyorsabb működés érdekében a socketet nonblocking módba kell hozni. Ez azt jelenti, hogy az adott socketen az egyébként blokkolódó függvények, mint pl. a read, vagy a connect rögtön visszatérnek. Ha ezt elmulasztjuk, a connect csak akkor fog visszatérni, ha már kiépült a kapcsolat, vagy ha már biztosan nem fog kiépülni. Ez pedig sok idő. Ha egy nonblocking socketre hívjuk meg a connect függvényt, az elindítja a kapcsolódási folyamatot, de mégsem nullával fog visszatérni, hanem EINPROGRESS hibát jelezve. Ez nem hiba, csak azt jelzi, hogy a kapcsolódási folyamatot elindítottuk, de még semmi sem biztos.

A nonblocking mód beállítása az fcntl függvénnyel történik.

Egy másik probléma a socket bezárásakor kezdődik. A socket a bezárás során több állapoton megy keresztül, mire ténylegesen bezáródna. (ld.: 2. szám: TCP alapú szerver kliens pár működése.) Ez azért probléma, mert kifuthatunk a rendelkezésre álló számú socket-ből, ha túl sok várakozik a számos bezárás felé vezető állapot valamelyikén. Ezért be kell állítani a SO_LINGER socket tulajdonságot. Ha ez be van állítva, akkor a close, vagy shutdown meghívásakor a socket csak azután záródik be, hogy minden bufferelt adat kiment, vagy letelt a beállított idő. Ha nullát állítunk be időnek, akkor azonnal zár. És mi ezt fogjuk tenni. A SO_LINGER opciót a setsockopt függvénnyel tudjuk beállítani.

A kapcsolódás:

int connect2port(int port) {   struct sockaddr_in host_addr;   int sd;   int old_fcntl;   l.l_onoff = 1; //linger be   l.l_linger = 0; //időt nullázzuk   //TCP socket letrehozasa   if ((sd=socket(AF_INET,SOCK_STREAM,6)) < 0)   {    perror("socket err");    exit(1);   }   //nonblocking állapot   old_fcntl = fcntl(sd, F_GETFL, 0); //eddigi állapot lekérdezése   fcntl(sd, F_SETFL, old_fcntl | O_NONBLOCK); //nonblocking hozzáadása   //linger beállítás   if (setsockopt(sd, SOL_SOCKET, SO_LINGER, (void *) &l,   sizeof(struct linger)))   perror("setsockopt");   bzero(&host_addr,sizeof(host_addr));   //host IP cimet atmasoljuk a host adatok koze   //host_entry globális   bcopy(host_entry->h_addr_list[0],&host_addr.sin_addr.s_addr,   host_entry->h_length);   host_addr.sin_family = host_entry->h_addrtype;   //megadjuk a portot   host_addr.sin_port = htons(port);   connect(sd, (struct sockaddr *)&host_addr, sizeof(struct sockaddr_in));   return sd; }

A socketek megnyitása után vizsgálni kell, hogy melyikre érkezett válasz. Erre azért van szükség, mert ahogy már említettem, a socketeket nonblocking állapotúak. Ha rendesen megvártuk volna, míg a connect függvény visszatér (blocking socketekkel), akkor már ott kiderült volna, hogy nyitottt-e az adott port. De erre nem érünk rá, így bonyolítjuk.

A többfeladatos szervereknél már használt select függvényt használjuk. Az adott socketen akkor jött létre a kapcsolat, ha tudunk rá írni, vagy tudunk róla olvasni. Viszont (a nonblocking dolog miatt), ha egy adott socketen hiba történt, és nem épült ki a kapcsolat, akkor is úgy jelez, mintha olvashatnánk róla. Szóval azon kívül, hogy figyeljük, lehet-e írni, vagy olvasni, az olvasásnál azt is meg kell nézni, hogy nem hiba történt-e. Erre a getsockopt függvény használható.

Ez a függvény a megadott kezdeti, és vég port között scan-nel:

void scanports(int start, int end) {   int *sock; //itt lesznek a socketek tárolva   int i,val,len;   fd_set rset, wset;   struct timeval tv;   len=sizeof(val);   /*hely lefoglalása*/   sock=(int *)malloc((end-start+1)*sizeof(int));   /*start-tól end portig kapcsolódás*/   for(i=start;i<=end;i++)     sock[i-start]=connect2port(i);   /*A connect2port függvény az adott portra a már ismert connect függvénnyel kapcsolódik. A socket nonblocking lesz!*/   /*Ebben a ciklusban várjuk az egyes socketeken a választ*/   while(1)   {     //A timeout, hogy mennyit várunk egy válasz érkezésére     tv.tv_sec = TIMEOUT;     tv.tv_usec = 0;     FD_ZERO(&rset);     FD_ZERO(&wset);     //a read, és write set-eket beállítjuk, minden socketet belerakunk     for(i=0;i<=end-start;i++)       if (sock[i]!=0)       {         FD_SET(sock[i],&rset);         FD_SET(sock[i],&wset);       }     //Ha nincs semmi, akkor ki     if (select(FD_SETSIZE, &wset, &rset, NULL, &tv) < 1) break;     for(i=0;i<=end-start;i++)       if (sock[i]!=0)       {         if (FD_ISSET(sock[i], &rset)) //lehet olvasni         {           if (!getsockopt(sock[i], SOL_SOCKET, SO_ERROR, &val, &len))           {             //nem történt hiba             if (!val) {               portfound(i+start); //port kiíratása               close(sock[i]); //socket bezárása               sock[i]=0; //socketet kivesszük a listából               continue; //tovább             }             //hiba történt             else {               close(sock[i]);               sock[i]=0;               continue;             }           }         }         //rávizsgálunk, lehet-e írni         for(i=0;i<=end-start;i++)           if (sock[i]!=0)           {             if (FD_ISSET(sock[i], &wset))             {               portfound(i+start);               close(sock[i]);               sock[i]=0;             }           }       }     free(sock);   }

A port kiírásakor azt is meg lehet nézni, hogy az adott port melyik szolgáltatáshoz tartozik elvileg. Ezt a getservbyport függvény tudja megmondani. A TIMEOUT értéket úgy kell megválasztani, hogy legyen ideje a csomagoknak visszaérni. Minél távolabbi szerverre használjuk, annál nagyobb kell, hogy legyen. A legjobb megoldás az lenne, ha a program végezne egy próba pinget, és ez alapján állítaná be az értéket. Most nekünk kell megtennünk, és átírni. Az sem mindegy, hogy egyszerre hány portra próbálunk kapcsolódni. Könnyen kifuthat a program a rendelkezésére álló nyitott socketek-ből. Ez a programban a MAXSOCKETS konstans. ld.: vanilla.c

Tehát ez a módszer a legegyszerűbb, leggyorsabb, de könnyen felismerhető. Az igazi előnye, hogy nem szükséges hozzá semmilyen privilégium, bármelyik felhasználó indíthat ilyen akciót, hiszen csak a connect függvényt használja.

Ez csak egy módszer a sok közül, de a továbbiakban párat még próbálok elővezetni. Aki "igazi" port scannert is akar látni, próbálja ki az nmap-ot.

Mellékelt forráskód.