WinPcap nám umožňuje posielať/prijímať celé pakety „priamou“ komunikáciou so sieťovou kartou. Ukážeme si, ako túto možnosť využiť v náš prospech. WinSock je sada hneď niekoľkých vrstiev protokolov, ktoré zjednodušujú prácu so sieťou.
Skoro každý, kto začínal programovať sieťové aplikácie, sa stretol s WinSock, používateľ nie je tým pádom nútený napr. spravovať TCP komunikáciu, ktorá sa nepriamo podieľa na prenose aplikačných dát. Taktiež nie ste nútený vedieť, aké sieťové karty máte nainštalované, ich MAC adresy, MAC adresy iných zariadení na sieti a pod. WinSock túto prácu robí za nás. No nie je to zadarmo. „Koho chlieb jete, toho pesničku spievate.“
Týmto mám na mysli obmedzenia, ktoré sú na WinSock aplikované novými verziami Windows alebo záplatami. Na Win XP SP2 a novších verziách máte napr. zakázané posielať TCP dáta, čo je pochopiteľné. Taktiež pochopiteľné je znemožnenie používateľovi zmeniť zdrojovú adresu UDP paketu. Tento prípad sa však teraz pokúsime obísť, a to použitím WinPcap (www.winpcap.org), ktorý priamo komunikuje so sieťovou kartou, teda všetky vaše dáta, či už majú zmysel zo sieťového hľadiska alebo nie, sú spracované.
Všetko, čo potrebujete na rozbehnutie WinPcap nájdete na stránke http://www.winpcap.org/install/default.htm.
Po nainštalovaní na váš počítač pribudne ovládač pre WinPcap, nejaké hlavičkové súbory a knižnice. Vám stačí poznať tri základné súbory, a to pcap.h
, wpcap.lib
a wpcap.dll
. Do svojho projektu, resp. zdrojového kódu, si vložte nasledovné:
#include
#include
#include
#include
#pragma comment(lib,"wpcap.lib")
#pragma comment(lib,"ws2_32.lib")
WinSock využijeme v prípadoch, kedy budeme potrebovať z textovej hodnoty IP adresy získať hodnotu číselnú a keď budeme premieňať čísla z usporiadania OS na usporiadanie sieťové.
Keďže budeme posielať UDP pakety, musíme poznať štruktúru UDP hlavičky, IP hlavičky a samotnej Ehternet hlavičky. Musíme si definovať jednotlivé štruktúry hlavičiek:
/* Ethernet hlavička*/
struct ethernet_hlavicka
{
unsigned char MACCiel[6]; // Cieľová Ethernet MAC adresa
unsigned char MACZdroj[6]; // Zdrojová Ethernet MAC adresa
unsigned short V3Protokol; // Protokol #3 vrstvy
};
/* IPv4 adresa */
struct ipv4_adresa
{
u_char bajt1;
u_char bajt2;
u_char bajt3;
u_char bajt4;
};
/* IPv4 hlavička */
struct ipv4_hlavicka
{
u_char ver_ihl; // Verzia (4 bity) + Veľkosť IPv4 hlavičky (4 bity)
u_char tos; // Typ služby
u_short tlen; // Celková dĺžka
u_short identification; // Identifikácia
u_short flags_fo; // Značky (3 bity) + Odsadenie fragmentu (13 bitov)
u_char ttl; // "TTL" - Time To Live
u_char proto; // Protokol
u_short crc; // Kontrolný súčet hlavičky
ipv4_adresa saddr; // Zdrojová adresa
ipv4_adresa daddr; // Cieľová adresa
};
/* UDP hlavička */
struct udp_hlavicka
{
u_short sport; // Zdrojový port
u_short dport; // Cieľový port
u_short len; // Dĺžka UDP hlavičky + dĺžka dát
u_short crc; // Kontrolný súčet
};
Následne si definujeme premennú adaptéra:
pcap_t * Adapter;
Túto premennú musíme inicializovať. Urobíme to funckiou pcap_t * pcap_open_live(const char * NazovZariadenia, int CastPaketuNaZachytenie, int PromiskuitnyMod, int VstupnyTimeout, char * ZasobnikPreChyboveSpravy);
. Názov zariadenia je úplný popis vašej sieťovej karty, v mojom prípade je to „\Device\NPF_{C6A2C5EC-AC66-4C80-986A-EF3A3168584C}“;. Časť paketu na zachytenie určuje, koľko dát z paketu bude zachytených. Promiskuitný mód hovorí sieťovej karte, aby pakety, ktoré nie sú určené jej, aj tak doručila systému, môžeme tak zachytiť pakety ostatných zariadení v sieti. Vstupný timeout určuje max. čas v milisekundách pre čítanie dát a zásobník pre chybové správy slúži na zapisovanie chybovej správy cez WinPcap. Aby ste nemuseli zdrojový kód prepisovať, odporúčam, aby ste mohli názov zariadenia vkladať ako parameter programu. Priamo uvediem aj funkciu, ktorá zobrazí zoznam aktívnych sieťových kariet, na ktoré je možné sa pripojiť:
void ZoznamZariadeni()
{
pcap_if_t * Zariadenia;
pcap_if_t * Zariadenie;
int i = 0;
char chyba[1024];
if (pcap_findalldevs(&Zariadenia, chyba) == -1)
std::cout << "Chyba pocas vykonavania pcap_findalldevs: " << chyba << std::endl;
for(Zariadenie=Zariadenia;Zariadenie!=0;Zariadenie=Zariadenie->next)
std::cout << ++i << ": \"" << Zariadenie->name << "\"" << std::endl;
if (i == 0)
std::cout << "Ziadne zariadenia" << std::endl;
pcap_freealldevs(Zariadenia);
}
int main(int argc, char * argv[])
{
pcap_t * Adapter;
if (argc < 2)
{
std::cout << "Pouzite ako: " << argv[0] << " \"zariadenie\"" << std::endl;
std::cout << "Pouzitelne zariadenia: " << std::endl;
ZoznamZariadeni();
system("PAUSE");
return 0;
}
char chyba[512];
Adapter = pcap_open_live(argv[1],65536,1,1000,chyba);
if (!Adapter)
{
std::cout << "Chyba pri otvarani adaptera: " << chyba << std::endl;
system("PAUSE");
return 0;
}
// Sem vložíme náš kód na vytvorenie a odoslanie paketu
pcap_close(Adapter);
system("PAUSE");
return 0;
}
Ak nenastavíte žiadny parameter pri spustení, program by vám mal vypísať zoznam zariadení, ktoré môžete použiť.
Nasleduje samotné vytvorenie a odoslanie paketu. Predtým, než začneme vypĺňať dáta, uvediem zostávajúce funkcie, ktoré budeme pri tvorbe potrebovať. A to funkciu na vypočítanie kontrolného súčtu pre IP hlavičku, funkciu na vytvorenie číselnej podoby IPv4 adresy z podoby textovej a funkcie na prevedenie textovej formy MAC adresy na číselnú.
Deklarácie (niektoré časti kódu sú napísané tak, aby poukázali na zámer funkcie):
static const char hexavachars[] = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F','a','b','c','d','e','f'};
char IsHexChar(char Char);
bool ConvertStrHEXCoupleToByte(char * couple, unsigned char * out);
bool GetMACAddressFromStr(const char * str, unsigned char * address);
unsigned short IPv4KontrolnySucet(const unsigned char * Data);
void InterpretIPAddress(const char * IPv4Adresa, ipv4_adresa * CiselnaIPv4Adresa);
Definície:
unsigned short IPv4KontrolnySucet(const unsigned char * Data)
{
unsigned short word16;
unsigned int sum = 0;
unsigned short i;
for (i=0;i>16)
sum = (sum & 0xFFFF)+(sum >> 16);
sum = ~sum;
return ((unsigned short) sum);
}
void InterpretIPAddress(const char * IPv4Adresa, ipv4_adresa * CiselnaIPv4Adresa)
{
using namespace std;
unsigned long iaddr = inet_addr(IPv4Adresa);
CiselnaIPv4Adresa->bajt1 = iaddr & 0xFF;
CiselnaIPv4Adresa->bajt2 = (iaddr >> 8) & 0xFF;
CiselnaIPv4Adresa->bajt3 = (iaddr >> 16) & 0xFF;
CiselnaIPv4Adresa->bajt4 = (iaddr >> 24) & 0xFF;
}
char IsHexChar(char Char)
{
for (unsigned char i=0;i<22;i++)
{
if (hexavachars[i] == Char)
{
if (i >= 16)
return i-6;
else
return i;
}
}
return -1;
}
bool ConvertStrHEXCoupleToByte(char * couple, unsigned char * out)
{
if (!couple || !out) return false;
char firstEq = IsHexChar(couple[0]);
char secondEq = IsHexChar(couple[1]);
if (firstEq != -1 && secondEq != -1)
{
*out = firstEq*16 + secondEq;
return true;
}
return false;
}
bool GetMACAddressFromStr(const char * str, unsigned char * address)
{
if (!str || !address) return false;
unsigned char cnt = 0;
char couple[2];
unsigned char last = 0;
for (int i=0;i= 6) return false;
if (ConvertStrHEXCoupleToByte(couple,&(address[last])))
last++;
else
return false;
}
cnt = 0;
break;
default:
if (cnt < 2)
{
couple[cnt] = str[i];
cnt++;
}
else
return false;
break;
}
}
if (cnt == 2)
{
if (last >= 6) return false;
if (ConvertStrHEXCoupleToByte(couple,&(address[last])))
last++;
else
return false;
}
if (last == 6)
return true;
else
return false;
}
Samotný paket budeme ukladať ako dáta o dĺžke = súčet dĺžok hlavičiek + veľkosť dát na odoslanie, čiže vytvoríme si zásobník na tieto dáta:
const char * DataNaOdoslanie = "Kuk :)";
int CelkovaDlzka = sizeof(ethernet_hlavicka)+sizeof(ipv4_hlavicka)+sizeof(udp_hlavicka)+strlen(DataNaOdoslanie);
char * Data = new char[CelkovaDlzka];
memset(Data,0,CelkovaDlzka);
DataNaOdoslanie môžu byť rôzne, text som použil len na demonštráciu. Teraz musíme tieto dáta nastavovať, od MAC adries v ethernetovej hlavičke až po UDP.
/* Definujeme jednotlivé pozície v pamäti */
ethernet_hlavicka * Ethernet = (ethernet_hlavicka *)Data;
ipv4_hlavicka * IPv4H = (ipv4_hlavicka *)(Data+sizeof(ethernet_hlavicka));
udp_hlavicka * UDP = (udp_hlavicka*)(Data+sizeof(ethernet_hlavicka)+sizeof(ipv4_hlavicka));
char * DatovePole = Data+sizeof(ethernet_hlavicka)+sizeof(ipv4_hlavicka)+sizeof(udp_hlavicka);
/* Nastavenie Ethernetovej časti */
GetMACAddressFromStr("00:00:00:00:00:00",Ethernet->MACCiel);
GetMACAddressFromStr("00:00:00:00:00:00",Ethernet->MACZdroj);
// 0x800 - IP hlavička
Ethernet->V3Protokol = htons(0x800);
/* Nastavenie IPv4 hlavičky */
// Verzia, dĺžka hlavičky
IPv4H->ver_ihl = 0x40 | (0xF & (sizeof(ipv4_hlavicka)/4));
IPv4H->tlen = htons(CelkovaDlzka - sizeof(ethernet_hlavicka));
IPv4H->ttl = 0xFF;
IPv4H->proto = IPPROTO_UDP;
InterpretIPAddress("0.0.0.0",&(IPv4H->saddr));
InterpretIPAddress("0.0.0.0",&(IPv4H->daddr));
IPv4H->crc = htons(IPv4KontrolnySucet((const unsigned char*)IPv4H));
/* Nastavenie UDP hlavičky */
UDP->sport = htons(0);
UDP->dport = htons(0);
UDP->len = htons(CelkovaDlzka - sizeof(ethernet_hlavicka) - sizeof(ipv4_hlavicka));
/* Skopírovanie dát na odoslanie */
memcpy(DatovePole,DataNaOdoslanie,strlen(DataNaOdoslanie));
V tomto kroku máme náš zásobník pre paket zaplnený hlavičkami a samotným textom „Kuk :)“. Všimnite si, že zdrojovú a cieľovú MAC adresu, ako aj zdrojovú a cieľovú IPv4 adresu, som nezadával, pretože predpokladám, že každý z nás má inú minimálne MAC adresu. Samozrejme, že ako MAC adresy, tak IP adresy môžu byť iné, než máte v skutočnosti, tak ako celý obsah dát vôbec nemusí reprezentovať správny paket, ale na odskúšanie funkčnosti by bolo dobré aspoň na začiatok použiť skutočné adresy. Pokiaľ neviete, ako zistiť svoju MAC a IP adresu, môžete ich nájsť takto: Štart » Spustiť » cmd.exe » ipconfig /all.
Vo výstupe hľadajte „Fyzická adresa“, bude to hodnota zložená zo 6 čísiel v hexadecimálnom tvare oddelených napr. znakom mínus „-“. Vaša IP adresa je za popisom „Adresa IP“.
Ďalej nastavte cieľovú MAC a IP adresu. Najjednoduchšie je nastaviť IP a MAC adresu predvolenej brány. IP adresu tejto „brány“ nájdete vo výstupe z ipconfig /all pod názvom „Predvolená brána“ alebo „Default gateway“. MAC adresu obdržíte príkazom arp –a: Štart » Spustiť » cmd.exe » arp -a
MAC adresu predvolenej brány nájdete podľa IP tejto brány. Ak sa však vo výstupe takýto záznam nenachádza, skúste spustiť príkaz ping [predvolená brána], čiže napr. ping 10.10.10.10, potom zadajte arp –a a nájdite zodpovedajúcu MAC adresu k IP adrese 10.10.10.10.
Pristúpime k samotnému odoslaniu paketu. V podstate je to jednoduchá jedna funkcia int pcap_sendpacket(pcap_t * Adapter, const u_char * Data, int CelkovaDlzka);
, ktorá, keď odošle paket, vráti 0.
if (pcap_sendpacket(Adapter,(const u_char*)Data,CelkovaDlzka) != 0)
std::cout << "Paket sa nepodarilo odoslat, chyba: " << chyba << std::endl;
A na záver nezabudneme odstrániť náš zásobník, kde sme uložili paket:
delete [] Data;
Keď program spustíte a všetko prebehne bez problémov, vaša sieťová karta by mala na sieť odoslať dáta zodpovedajúce paketu, ktorý ste navrhli. Na to, aby ste videli, či sa tak naozaj stalo, je dobrý program Wireshark.
Zhrnutie
Spôsob posielania dát, ktorý som uviedol, nie je moc efektívny. V prípadoch, kedy potrebujete zároveň čítať dáta, ktoré sú odpoveďou na vaše dotazy, je použitie takéhoto spôsobu zaťažujúci. Pokiaľ teda nemáte záujmy odosielať dáta anonymne, WinSock je najlepším možným riešením, ktoré vývojári pod Windows nájdu.
- Zdrojový kód programu: zdrojový kód
- Triedy, s ktorými je práca s paketmi jednoduchšia: triedy