显示标签为“Checksum”的博文。显示所有博文
显示标签为“Checksum”的博文。显示所有博文

2007年7月31日星期二

小D.O.S.了一把

这两天在windows和FreeBSD下面分别写了2个半程序,用来搞"破坏"的.:) 我这个人30多年来一直比较老实,这些年来主要为单位工作写一些应用程序,把青春浪费在不应该浪费的地方了,我爸前一个礼拜向别人说我:"他(指我)30 岁才学会骂人.."可见我以前多么愚不可及阿... 但是我是属于闷痞一类的人,就是说:外表是个老实人,但是内心里面是"坏"得一塌糊涂!!^_^

为什么说是2个半程序呢,其实是2个,其中一个在windows和freebsd下面用不同的库写了一遍,因为在windows下面写完的时候才发现我将来要在freebsd下面完成这个"破坏"活动...所以,不得已,又重新写了...

我本次的目标是市内的一个ZF网站,因为它的配置比较落后,而且有95%以上的可能,它已经被人攻破,因为它返回的ping值惊人的大,晚上一般竟 然有300-500ms,要知道那网站上面其实是没有啥东西的,没有大量的资源让人下载,没有人看电影,因为电影站在我们市的另外一个地方.网站确实装了 防火墙,只露出80端口,但是它上面加装的站台程序是改自一个开源软件的,这个东西以前出过漏洞的. 当然这仅仅是猜测. 另外在traceroute的过程中,发现半程的时候已经发现ping值非常大了,可能当中的某个路由器是瓶颈.

我的第一个程序稍微放了40Mbps的流量上去,它的带宽就完全就被堵上了..向它附近的若干相邻的站发,也产生类似效果..结论是带宽太小.. 嘘.....小声..,如果这个消息被某些商家听到,他们就要上门给我们的父母官推销好一点的设备和宽一点的腰带了:). 当然我如果撤流量,它也就恢复了.. 我的第二个程序就有点普通了,简单的syn flood,修改自以前下载的代码,但是那个代码有错,后面说. 这个DOS是稍微差一点的web服务器都无法抵挡的,所以,我稍微发了500万个包上去(其中99%成功),它就完蛋了,现在我写文章的时候距离发送结束 已经过去30分钟,它的web服务还是无法连接:b ,(看来它的机器好不了哪去...),估计得明天有人重新启动服务才行...

下面来说说那个syn flood代码,这个代码很早就有了,网上稍微搜索一下就有了,我不放在这里害人了,因为它写得有问题. 我是前几年偶尔收藏的,一直没有用过(好孩子阿..), 最近编译的时候才发现不行.

问题一: FreeBSD 的 sendto(fd, buffer,len,flags,addr,len1) 函数对 len有个要求,就是填写ip头的时候,ip->ip_len不能用htons(len),而是直接用len,host byte order,否则sendto()的时候会有:invalid argument 错误,这个是我调试半天没有结果,最后从一个邮件列表里面找到解决方案的:

http://mail-index.netbsd.org/tech-net/1999/01/03/0001.html

Subject: Re: sendto: Invalid argument - help!
To: Hubert Feyrer
From: Dennis Ferguson
List: tech-net
Date: 01/03/1999 18:38:07

> trying sendto(4, packet, 28, 0, 127.0.0.1, 16)sendto in send_ip_raw:
> sendto(4, packet, 28, 0, 127.0.0.1, 16) => Invalid argument
>
> There seems to be an endianness problem somewhere, but I really don't have

Leave the packet length in the IP header (i.e. ip->ip_len) and the
fragment offset (i.e. ip->ip_off) in host byte order. BSD boxes do it
this way, Linux does it the other (more sensible) way.

Dennis Ferguson



问题二:它的tcp的checksum的计算方法是错误的,压根没有用到伪头部,而如果checksum置0的话,系统并不自动生成,而ip头如果置0的话,系统会自动运算,这一点在FreeBSD上面是这样的.

问题三:它提供的checksum子程序也是错误的

这个错误很隐蔽,正确的算法是这样的:

//Correct Codes : from print-tcp.c of tcpdump 正确的算法

u_short in_cksum(const u_short *addr, register u_int len, int csum)
{
int nleft = len;
const u_short *w = addr;
u_short answer;
int sum = csum;

/*
* Our algorithm is simple, using a 32 bit accumulator (sum),
* we add sequential 16 bit words to it, and at the end, fold
* back all the carry bits from the top 16 bits into the lower
* 16 bits.
*/
while (nleft > 1) {
sum += *w++;
nleft -= 2;
}
if (nleft == 1)
{
sum += (unsigned short) *(unsigned char *)w; //Modified ommit htons by regshot.

}

/*
* add back carry outs from top 16 bits to low 16 bits
*/
sum = (sum >> 16) + (sum & 0xffff); /* add hi 16 to low 16 */
sum += (sum >> 16); /* add carry */
answer = ~sum; /* truncate to 16 bits */
return (answer);
}
---------------------------------------------------

//Wrong codes: 错误的算法

unsigned short check_sum(unsigned short *addr,int len)
{
register int nleft=len;
register int sum=0;
register short *w=addr; <<---- Error!!
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);
}

-------------------------------------

粗看这两个代码基本一样,但是注意看上面红色的部分,这个指针被定义为 :short * , 而不是unsigned short * ,所以在做加法的时候出错l

所以,在编译这个东西的时候费了很大的周折,因为我曾经相信它是对的.....

在 http://www.xfocus.net/articles/200106/208.html 上面转载有一篇 2001年 补天网的 shotgun实现的syn flooder (Windows 2000)代码,我虽然没有用,但感觉还是不错的,至少它的tcp checksum算法不象我上面用的代码那样有问题,另外他 对于 如何提高checksum算法来使得循环加快的说法使我有了兴趣,他的原话是这样的:

[quote]稍微动动脑筋我们就会发现,想对SYN Flooder程序进行优化是很简单的,从程序构架来看,攻击时循环内的代码主要是进行校验和计算与缓冲区的填充,一般的思路是提高校验和计算的速度,我 甚至见过用汇编代码编写的校验和函数,实际上,有另外一个变通的方法可以轻松实现优化而又不需要高深的编程技巧和数学知识,(老实说吧,我数学比较差: P),我们仔细研究了两个不同源地址的TCP SYN报文后发现,两个报文的大部分字段相同(比如目的地址、协议等等),只有源地址和校验和不同(如果为了隐蔽,源端口也可以有变化,但是并不影响我们 算法优化的思路),如果我们事先计算好大量的源地址与校验和的对应关系表(如果其他的字段有变化也可以加入这个表),等计算完毕了攻击程序就只需要单纯的 组合缓冲区并发送(用指针来直接操作缓冲区的特定位置,从事先计算好的对应关系表中读出数据,替换缓冲区相应字段),这种简单的工作完全取决于系统发送 IP包的速度,与程序的效率没有任何关系,这样,即使是CPU主频较低的主机也能快速的发送大量TCP SYN攻击包。如果考虑到缓冲区拼接的时间,甚至可以定义一个很大的缓冲区数组,填充完毕后再发送(雏鹰给这种方法想了一个很贴切的比喻:火箭炮装弹虽然 很慢,但是一旦炮弹上膛了以后就可以连续猛烈地发射了:)。-- by shotgun[/quote]

我感觉利用缓冲区虽然可行,但是我比较懒的,又要写额外的代码存起对应关系...比较麻烦 ...我的机器比他2001年文中的"PIII 550MHz+128MB+100Mbps" 还要旧,只有他的一半,所以,我要么写成汇编,要么另外找办法.我 的程序就想了一个方法,来加快checksum速度,其原理就是: 将pseudo hdr的src addr和tcp src port字段共 6个字节清空 先计算一个伪的~fchksum,每次循环用一个random()得到随机的src addr,src port,填入,然后in_cksum只要带这个~fchksum作为初始值计算这6个字节的总checksum就可以了. 我已经实现了,checksum正确,^_^. 如果有时间我还想继续优化一下,但估计空间不大了,另外 sendto有1%的报错为"permission denied",估计是网卡太慢的缘故..

写完的时候,那个站还没有起来...不知道咋搞的... :) -- by regshot 20060809

IP数据包的校验和算法(载)

IP数据包的头信息格式:
+-------------------------------------------------+
| 版本 (4位) |
+-------------------------------------------------+
| 首部长度(4位) |
+-------------------------------------------------+
| 服务类型(TOS)8位 |
+-------------------------------------------------+
| 数据包总长度(16位) |
+-------------------------------------------------+
| 标识ID号(16位) |
+-------------------------------------------------+
| 标志位(3位) |
+-------------------------------------------------+
| 片偏移(13位) |
+-------------------------------------------------+
| 生存时间(TTL)(8位) |
+-------------------------------------------------+
| 协议类型 (8位) |
+-------------------------------------------------+
| 首部校验和(16位) |
+-------------------------------------------------+
| 源IP地址(32位) |
+-------------------------------------------------+
| 目的IP地址 (32位) |
+-------------------------------------------------+
* IP选项(若有) (32位) *
+-------------------------------------------------+
* 数据 *
+-------------------------------------------------+

这里要说的是首部校验和字段。
在发送数据时,为了计算数IP据报的校验和。应该按如下步骤:
(1)把IP数据报的首部都置为0,包括校验和字段。
(2)把首部看成以16位为单位的数字组成,依次进行二进制反码求和。
(3)把得到的结果存入校验和字段中。
在接收数据时,计算数据报的校验和相对简单,按如下步骤:
(1)把首部看成以16位为单位的数字组成,依次进行二进制反码求和,包括校验和字段。
(2)检查计算出的校验和的结果是否等于零。
(3)如果等于零,说明被整除,校验是和正确。否则,校验和就是错误的,协议栈要抛弃这个数据包。

首先,查看了Linux 2.6内核中的校验算法,使用汇编语言编写的,显然效率要高些。代码如下:
unsigned short ip_fast_csum(unsigned char * iph,
unsigned int ihl)
{
unsigned int sum;

__asm__ __volatile__(
"movl (%1), %0 ;\n"
"subl $4, %2 ;\n"
"jbe 2f ;\n"
"addl 4(%1), %0 ;\n"
"adcl 8(%1), %0 ;\n"
"adcl 12(%1), %0 ;\n"
"1: adcl 16(%1), %0 ;\n"
"lea 4(%1), %1 ;\n"
"decl %2 ;\n"
"jne 1b ;\n"
"adcl $0, %0 ;\n"
"movl %0, %2 ;\n"
"shrl $16, %0 ;\n"
"addw %w2, %w0 ;\n"
"adcl $0, %0 ;\n"
"notl %0 ;\n"
"2: ;\n"
/* Since the input registers which are loaded with iph and ihl
are modified, we must also specify them as outputs, or gcc
will assume they contain their original values. */
: "=r" (sum), "=r" (iph), "=r" (ihl)
: "1" (iph), "2" (ihl)
: "memory");
return(sum);
}

在这个函数中,第一个参数显然就是IP数据报的首地址,所有算法几乎一样。需要注意的是第二个参数,它是直接使用IP数据报头信息中的首部长度字段,不需要进行转换,因此,速度又快了(高手就是考虑的周到)。使用方法会在下面的例子代码中给出。

第二种算法就非常普通了,是用C语言编写的。我看了许多实现网络协议栈的代码,这个算法是最常用的了,即使变化,也无非是先取反后取和之类的。考虑其原因,估计还是C语言的移植性更好吧。下面是该函数的实现:
unsigned short checksum(unsigned short *buf,int nword)
{
unsigned long sum;

for(sum=0;nword>0;nword--)
sum += *buf++;
sum = (sum>>16) + (sum&0xffff);
sum += (sum>>16);

return ~sum;
}

这里我要把我的惨痛的经历也要说一下,为了研究IP校验和的算法,我根据算法也进行了代码编写,可是结果总是有8位不一样,郁闷了好久,最后还是 David L .Stevens给我了答案(不要误会,是在他的书中找到的答案,呵呵)。那就是现在我们所用的机器设备大多数是使用二进制补码算法进行计算的。因此,仅 仅简单的累加得出的校验和并不是正确的结果。
下面就是IP数据报首部校验和算法的代码示例:

#include
#include
#include
#include
#include


#define ETH_P_LENGTH 65535
#define ETHERNET_MAX_LEN 1500
#define ETHERNET_MIN_LEN 46

unsigned short ip_fast_csum(unsigned char * iph, unsigned int ihl);
unsigned short checksum(unsigned short *buf,int nword);


//--------------------------------------------------------------------
// Main function
//
// Do all if it can do
//
//--------------------------------------------------------------------

int main(int argc,char *argv[])
{
int listenfd;
int nbyte;
char buf[ETH_P_LENGTH];
struct ethhdr *eth = NULL;
struct iphdr *ip = NULL;
short chk;

//
// Print banner
//
printf("\n\tSendArp v1.0 - scan IP and MAC\n");
printf("\tNsfocus - www.nsfocus.com\n");
printf("\tby David Zhou\n");
printf("\tDate : 2006/01/19\n\n");

if ((listenfd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL))) < 0)
{
printf("Call socket() function error\n");
return 1;
}

for (;;)
{
if ((nbyte = recv(listenfd, buf, ETH_P_LENGTH, 0)) > 0)
{
struct ethhdr *eth = (struct ethhdr *)buf;
if(ntohs(eth->h_proto) == ETH_P_IP)
{ // EtherNet frame
// print ip sum
ip = (struct iphdr *)&buf[14];
printf("IP CheckSum = 0x%04X\n",ntohs(ip->check));
//verify ip checksum
chk = checksum((unsigned short*)ip,10);
printf("Verify CheckSum = 0x%04X\n\n",ntohs(chk));
//
// reset check to calc self
//
ip->check = 0;
// 2.6 kernel
chk = ip_fast_csum((unsigned char *)ip,ip->ihl);
printf("Calc CheckSum = 0x%04X - %d\n",ntohs(chk),ip->ihl);
// coustom calc
chk = checksum((unsigned short*)ip,10);
printf("Calc CheckSum = 0x%04X\n\n",ntohs(chk));
}
}
}
return 0;
}


unsigned short checksum(unsigned short *buf,int nword)
{
unsigned long sum;

for(sum=0;nword>0;nword--)
sum += *buf++;
sum = (sum>>16) + (sum&0xffff);
sum += (sum>>16);

return ~sum;
}

/*
* This is a version of ip_compute_csum() optimized for IP headers,
* which always checksum on 4 octet boundaries.
*
* By Jorge Cwik , adapted for linux by
* Arnt Gulbrandsen.
*/
unsigned short ip_fast_csum(unsigned char * iph,
unsigned int ihl)
{
unsigned int sum;

__asm__ __volatile__(
"movl (%1), %0 ;\n"
"subl $4, %2 ;\n"
"jbe 2f ;\n"
"addl 4(%1), %0 ;\n"
"adcl 8(%1), %0 ;\n"
"adcl 12(%1), %0 ;\n"
"1: adcl 16(%1), %0 ;\n"
"lea 4(%1), %1 ;\n"
"decl %2 ;\n"
"jne 1b ;\n"
"adcl $0, %0 ;\n"
"movl %0, %2 ;\n"
"shrl $16, %0 ;\n"
"addw %w2, %w0 ;\n"
"adcl $0, %0 ;\n"
"notl %0 ;\n"
"2: ;\n"
/* Since the input registers which are loaded with iph and ihl
are modified, we must also specify them as outputs, or gcc
will assume they contain their original values. */
: "=r" (sum), "=r" (iph), "=r" (ihl)
: "1" (iph), "2" (ihl)
: "memory");
return(sum);
}