Az előző számban ismertetett módszer nagyon kényelmes egyszerűsége miatt és amiatt, hogy egyszerű felhasználóként is futtatható. Annak érdekében viszont, hogy kevésbé könnyen detektálható legyen a ténykedésünk más módszert kell keresnünk, és mélyebbre kell ásni a tcp protokoll működésében. A következőkben tárgyalt, és ehhez hasonló technikákat "stealth scan"-nek nevezik.

A tcp működéséről már volt szó. Tudjuk, hogy kapcsolat orientált protokoll, és az IP-re épül. Tehát minden akció IP csomagok formájában testesül meg, ezt pedig ki is fogjuk használni. Emlékeztetőül egy kis ábra:

Az ábra a TCP kapcsolat kiépülését, és lezárását tartalmazza. (Bővebb magyarázatért ld.: TCP alapú kliens-szerver pár működése c. számot, mely a sorozat 2. részében található.)

Az ábráról látszik, hogy többféleképpen is megvizsgálhatjuk egy port nyitva van-e, anélkül, hogy kiépítenénk a kapcsolatot. Ehhez persze nem az ábrán is jelölt függvényeket kell használnunk, hanem magunk fogjuk felépíteni az IP csomagokat.

Egy biztos módszer például az, hogy a kliens, azaz mi, egy SYN csomagot küld (ábrán SYN J), mintha kapcsolódni akarna. Ez a SYN scan. Ha kap választ (ábrán SYN K), akkor bizonyos, hogy a port nyitva van. Egyetlen csomagot kellett csak elküldeni, és már meg is tudtuk, hogy mi a helyzet. Gyakorlatban persze, ha nem érkezik válasz, az nem szükségszerűen jelenti a port bezártságát. Elképzelhető, hogy az egy szem csomagunk, vagy a válaszcsomag elveszett a hálózat útvesztőjében. Ezért kis várakozás után meg lehet próbálni az újbóli elküldést.

Ugyanezt a játékot el lehet játszani a kapcsolat bezárásakor. Ez a FIN scan. A kliens küld egy FIN csomagot, végjelet, mintha zárni akarná a kapcsolatot (ábrán FIN M). A szervernek, ha az adott port nyitva van, kötelessége erre válaszolni (ábrán ack FIN M+1). Még akkor is, ha nem létezett kapcsolat a klienssel azon a porton. Ez nem mindig így van, bizonyos tcp/ip implementációkban csak akkor jön válasz, ha létezett kapcsolat. Linux-on mindig működik.

Az is érdekes lehet, ha egy üres, (egy flag sincs beállítva) TCP csomagot küldünk a szervernek. Ez a NULL scan. Ekkor, ha a port nyitva van, választ fogunk kapni egy hibaüzenet formájában. A hibaüzenetből pedig következtetni lehet a szerveren futó operációs rendszerre.

Az ábra szinte minden pontján belenyúlhatunk, akár ACK csomagot is küldhetünk, ez az ACK scan. Számos variáció létezik, azonban az közös bennük, hogy mind a TCP csomagot variálják meg.

A konkrét megvalósításhoz ismernünk kell a TCP, és IP fejléc felépítését. Az IP fejlécről már esett szó az IP spoofing-al kapcsolatban, nézzük a TCP fejléc felépítését.
 

Forrás Port
Célport
Sorszám
Ráültetett nyugta
Fejréc hossz
URG
ACK
EOM
R

S

T

SYN
F

I

N

Ablak
Ellenőrző összeg
Sürgősségi mutató
Opciók
ADAT

Az ábrán egy sor 32 bájt. A minimális TCP fejléc 20 bájtos, ez nem tartalmaz opciókat. A sorszám 32 bites, a TCP minden bájtot külön megszámoz. A ráültetett nyugta jelentősége, hogy nem kell külön csomag a nyugtázáshoz, az ugyanoda haladó egyéb csomagokra ráültethető. A fejléc hossz azt tartalmazza, hogy a fejléc hány 32 bites szót tartalmaz. Egyszerre több csomag is lehet úton, az ablak azt adja meg, hogy hány bájtot lehet még elküldeni. Számunkra most a SYN, illetve FIN biteknek van jelentőségük. Ezek a már említett feladatokat hajtják végre. Vegyük észre, hogy a TCP fejléc nem tartalmazza a forrás, és cél gép címét, csupán a port-okat. Ez kézenfekvő, hiszen a TCP fejlécet egy IP fejléc fog megelőzni. Tehát az összeállítandó teljes csomag:
 

IP fejléc
TCP fejléc
Adatrész

Esetünkben az adatrész üres lesz.

A TCP fejlécet leíró struktúra a netinet/tcp.h -ban van leírva. A struktúra kicsit leegyszerűsítve:

struct tcphdr
{
  u_int16_t th_sport; /* source port */
  u_int16_t th_dport; /* destination port */
  tcp_seq th_seq; /* sequence number */
  tcp_seq th_ack; /* acknowledgement number */
  u_int8_t th_x2:4; /* (unused) */
  u_int8_t th_off:4; /* data offset */
  u_int8_t th_flags;
  /*flageket leiro konstansok*/
  # define TH_FIN 0x01
  # define TH_SYN 0x02
  # define TH_RST 0x04
  # define TH_PUSH 0x08
  # define TH_ACK 0x10
  # define TH_URG 0x20
  u_int16_t th_win; /* window */
  u_int16_t th_sum; /* checksum */
  u_int16_t th_urp; /* urgent pointer */
};

A struktúra akkor néz ki így, ha előtte definiáljuk a

__FAVOR_BSD
konstansot. Ld. A header file.

A csomag felépítése:

int send_tcp_raw_socket(unsigned short sport, unsigned short dport, unsigned int seq, unsigned int ack, unsigned char flags)
{
char packet[TOTLEN];
struct iphdr *ip;
struct tcphdr *tcp;

  bzero(packet,TOTLEN);
  ip=(struct iphdr*)&packet;
  tcp=(struct tcphdr*)&packet[sizeof(struct iphdr)];

  //ip fejlec
  ip->version = 4;
  ip->ihl = 5;
  ip->ttl = 254;
  ip->protocol = IPPROTO_TCP;
  ip->tot_len = htons(TOTLEN);
  bcopy((char *)&dest.sin_addr, &ip->daddr, sizeof(ip->daddr));
  bcopy((char *)&source.sin_addr, &ip->saddr, sizeof(ip->saddr));
  ip->check=in_cksum((u_short *)ip,sizeof(struct iphdr));

  //tcp fejlec
  tcp->th_sport=htons(sport);
  tcp->th_dport=htons(dport);
  tcp->th_seq=htonl(seq);
  tcp->th_ack=htonl(ack);
  tcp->th_off=5;
  tcp->th_flags=flags;
  tcp->th_win=1024;
  tcp->th_sum = in_cksum((u_short *)tcp, sizeof(struct tcphdr));

 

A socket megnyitása

Ahhoz, hogy a csomagot közvetlenül lehessen küldeni, RAW típusú socketet kell létrehozni. Ezért nem árt, ha root-ként fut a program.

rawsock=socket(AF_INET, SOCK_RAW, IPPROTO_RAW);

Erről a socketről nem tudjuk visszaolvasni az eredményt, kell egy másikat nyitni, amiben megadjuk, hogy a tcp csomagokat kérjük:

tcpsock=socket(AF_INET, SOCK_RAW, IPPROTO_TCP);
 

Az első socket-en lehet elküldeni a csomagot, a második socketen, pedig várni, hogy jött-e válasz.