前言
最近,有一同学给我发来一投票的链接,当然希望我帮他投某某的票了o(︶︿︶)o
我立马投了票, 再投第二下那时
限制了IP了~
我却突然萌生一个想法, 如果我伪造TCP数据包, 伪造TCP头上的IP地址,不是可以想多少票,就多少票了~
PS:因为之前看过有关SYN攻击的描述,知道可以发出大量伪造的SYN数据包,使到服务器建立大量半连接,占用服务器的资源
先从简单的udp 数据包开始. 立马找了一段以前udp监听的代码,用它来负责测试.
首先,发送数据包的话, 我首先想到的是raw socket.
马上从网上拷了一段代码, 对我的测试代码发, 失败. 又调了老半天,还是失败.
网上的原因说到: 操作系统对raw socket的限制 - -!
至于能不能使用raw socket 发送数据包, 我就没有细细研究了.
我发现sendto一个数据包还要填地址和端口... 既然是建立在IP那一层, 为什么还要填个端口.
//===========================================================================//
接着, 我想到以前用过一下的库 --- Winpcap.
Winpcap能发数据链路层的数据!
使用Winpcap发送数据
//获取发送的句柄了
pcap_t* adhandle = NULL;
char errbuf[PCAP_ERRBUF_SIZE] = { 0 };
pcap_if_t *alldevs = NULL;
if(pcap_findalldevs(&alldevs, errbuf) == -1)
return;
if((adhandle = pcap_open(alldevs->name, 0x10000, PCAP_OPENFLAG_PROMISCUOUS, 1000, NULL, errbuf)) == NULL)
return;
pcap_freealldevs(alldevs);
//发送数据包
if(pcap_sendpacket(adhandle, (const u_char*)buf, totalLen) == -1)
{
}
接着就组装数据包了.
先看一下他们的校验和(来自网上的):
◆当发送IP包时,需要计算IP报头的校验和:
1、把校验和字段置为0;
2、对IP头部中的每16bit进行二进制求和;
3、如果和的高16bit不为0,则将和的高16bit和低16bit反复相加,直到和的高16bit为0,从而获得一个16bit的值;
4、将该16bit的值取反,存入校验和字段。
◆当接收IP包时,需要对报头进行确认,检查IP头是否有误,算法同上2、3步,然后判断取反的结果是否为0,是则正确,否则有错。
实现代码如下:
u_int16_t in_cksum (u_int16_t * p, int psize)
{
u_int32_t ret = 0;
while (psize > 1)
{
ret += *p++;
psize -= 2;
}
if (psize == 1)
ret += *(u_int8_t*)p;
ret = (ret >> 16) + (ret & 0xffff);
ret += (ret >> 16);
return ~ret;
}
下面看一下IPv4数据包头(简要说明而已, 如果想相信了解每一个项是怎么用, 建议查一下书吧!!)
版本:4
报头长度: 首部长/4(没选项就是5了)
服务类型:没用,置0
总长度:首部长度+数据长度
标识,标志,片偏移: IP数据包分片相关,将标志赋0x2就是不分片了
生存时间:0xff(255)
协议:看运输层,TCP:6, UDP:17
头校验和: 就是使用上面的算法计算头部的校验和, 不包括数据部分
源地址,目的地址: ..
选项就忽略吧~
真正的UDP包就只有4个字段而已
源端口,目的端口: ..
长度: UDP头+数据的长度
校验和:这个校验和的算法,要加上伪头部和数据来算,如上图.其中伪头部中的长度=UDP总长度
数据部分: 不一定是16位的倍数, 上图只是计算检验和那时的情况
这里, 我成功把数据包发给了测试程序 :-) (代码等一下再贴)
下面是TCP的头.
源端口,目的端口: ..
序号: 你现在发的包的开始序号
确认号: 你收到对方发的包的序号个数
头部: 就是头部的长度, 要除以4了
保留: 置0
URG:使用紧急指针,不用,置0
ACK:使用确认号
PSH:尽快提交上层
RST:拒绝连接,重置连接
SYN:用于建立连接
FIN:用于建立连接
窗口大小:表示自己还能接收多少数据
检验和:如UDP的检验和的算法, 但是要把伪头部中协议置为6
紧急指针:不用,置为0
选项:不用, 它主要有MSS, SACK的功能
下面贴代码
#define HAVE_REMOTE
#include <pcap.h>
#pragma comment(lib, "wpcap.lib")
#pragma comment(lib, "ws2_32.lib")
u_int16_t crc_checksum(u_int16_t* p, int psize)
{
u_int32_t ret = 0;
while (psize > 1)
{
ret += *p++;
psize -= 2;
}
if (psize == 1)
ret += *(u_int8_t*)p;
ret = (ret >> 16) + (ret & 0xffff);
ret += (ret >> 16);
return ~ret;
}
struct ether_header{
u_int8_t ether_dhost[6];
u_int8_t ether_shost[6];
u_int16_t ether_type; //IP协议,为0x0800
};
struct ip_header
{
u_int8_t version_headerLen;
u_int8_t servicetype;
u_int16_t totalLen;
u_int16_t identification;
u_int16_t flags_fragOffset;
u_int8_t ttl;
u_int8_t protocol;
u_int16_t checksum;
u_int32_t saddr;
u_int32_t daddr;
};
struct udp_header
{
u_int16_t sport;
u_int16_t dport;
u_int16_t totalLen;
u_int16_t checksum;
};
struct tcp_header
{
u_int16_t sport;
u_int16_t dport;
u_int32_t seq;
u_int32_t ack_seq;
u_int16_t dataOffset_reserve_flags;
u_int16_t window;
u_int16_t checksum;
u_int16_t urg_ptr;
};
struct psd_header
{
u_int32_t saddr;
u_int32_t daddr;
u_int8_t zero;
u_int8_t protocol;
u_int16_t len;
};
void* add_bytes_addr(void* st, int bytes)
{
return (void*)(((char*)st) + bytes);
}
pcap_t* adhandle = NULL;
void send_ether_data_from_ip(const char* d, const int dlen)
{
int totalLen = sizeof(ether_header) + dlen;
char* buf = new char[totalLen];
ether_header* etherhdr = (ether_header*)add_bytes_addr(buf, 0);
char* data = (char*)add_bytes_addr(etherhdr, sizeof(ether_header));
memcpy(data, d, dlen);
etherhdr->ether_shost[0] = 0x00;
etherhdr->ether_shost[1] = 0xE0;
etherhdr->ether_shost[2] = 0xB0;
etherhdr->ether_shost[3] = 0xE7;
etherhdr->ether_shost[4] = 0xA6;
etherhdr->ether_shost[5] = 0xDD;
etherhdr->ether_dhost[0] = 0xc8;
etherhdr->ether_dhost[1] = 0x3a;
etherhdr->ether_dhost[2] = 0x35;
etherhdr->ether_dhost[3] = 0x2c;
etherhdr->ether_dhost[4] = 0x07;
etherhdr->ether_dhost[5] = 0x00;
etherhdr->ether_type = htons(0x0800);
if(pcap_sendpacket(adhandle, (const u_char*)buf, totalLen) == -1)
{
}
delete[] buf;
}
void send_ip_data(u_int saddr, u_int daddr, u_char protocol, const char* d, int dlen)
{
int totalLen = sizeof(ip_header) + dlen;
char* buf = new char[totalLen];
ip_header* iphdr = (ip_header*)add_bytes_addr(buf, 0);
char* data = (char*)add_bytes_addr(iphdr, sizeof(ip_header));
memcpy(data, d, dlen);
iphdr->version_headerLen = (4<<4) | 5;
iphdr->servicetype = 0;
iphdr->totalLen = htons(totalLen);
iphdr->identification = htons(0);
iphdr->flags_fragOffset = htons((2<<13) | 0);
iphdr->ttl = 0xff;
iphdr->protocol = protocol;
iphdr->checksum = 0;
iphdr->saddr = saddr;
iphdr->daddr = daddr;
iphdr->checksum = crc_checksum((u_int16_t*)iphdr, sizeof(ip_header));
send_ether_data_from_ip(buf, totalLen);
delete[] buf;
}
u_int16_t calc_psd_checksum(u_int32_t saddr, u_int32_t daddr, u_int8_t protocol, const char* d, int dlen)
{
int totalLen = sizeof(psd_header) + dlen;
char* buf = new char[totalLen];
psd_header* psdhdr = (psd_header*)add_bytes_addr(buf, 0);
char* data = (char*)add_bytes_addr(psdhdr, sizeof(psd_header));
memcpy(data, d, dlen);
psdhdr->saddr = saddr;
psdhdr->daddr = daddr;
psdhdr->zero = 0;
psdhdr->protocol = protocol;
psdhdr->len = htons(dlen);
u_int16_t ret = crc_checksum((u_int16_t*)buf, totalLen);
delete[] buf;
return ret;
}
void send_udp_data(u_int32_t saddr, u_int16_t sport, u_int32_t daddr, u_int16_t dport, const char* d, int dlen)
{
int totalLen = sizeof(udp_header) + dlen;
char* buf = new char[totalLen];
udp_header* udphdr = (udp_header*)add_bytes_addr(buf, 0);
char* data = (char*)add_bytes_addr(udphdr, sizeof(udp_header));
memcpy(data, d, dlen);
udphdr->sport = htons(sport);
udphdr->dport = htons(dport);
udphdr->totalLen = htons(totalLen);
udphdr->checksum = 0;
udphdr->checksum = calc_psd_checksum(saddr, daddr, IPPROTO_UDP, buf, totalLen);
send_ip_data(saddr, daddr, IPPROTO_UDP, (const char*)udphdr, totalLen);
delete[] buf;
}
inline u_int8_t tcp_flags(u_int8_t urg, u_int8_t ack, u_int8_t psh, u_int8_t rst, u_int8_t syn, u_int8_t fin)
{
return (urg << 5) | (ack << 4) | (psh << 3) | (rst << 2) | (syn << 1) | fin;
}
void send_tcp_data(u_int32_t saddr, u_int16_t sport, u_int32_t daddr, u_int16_t dport,
u_int32_t seq, u_int32_t ack_seq, u_int8_t flags,
const char* d, int dlen)
{
int totalLen = sizeof(tcp_header) + dlen;
char* buf = new char[totalLen];
tcp_header* tcphdr = (tcp_header*)add_bytes_addr(buf, 0);
char* data = (char*)add_bytes_addr(tcphdr, sizeof(tcp_header));
memcpy(data, d, dlen);
tcphdr->sport = htons(sport);
tcphdr->dport = htons(dport);
tcphdr->seq = htonl(seq);
tcphdr->ack_seq = htonl(ack_seq);
tcphdr->dataOffset_reserve_flags =
htons((u_short)(( (sizeof(tcp_header)/4) << 12) | (0<<6) | flags));
tcphdr->window = htons(8196);
tcphdr->checksum = 0;
tcphdr->urg_ptr = 0;
tcphdr->checksum = calc_psd_checksum(saddr, daddr, IPPROTO_TCP, buf, totalLen);
send_ip_data(saddr, daddr, IPPROTO_TCP, (const char*)tcphdr, totalLen);
delete[] buf;
}
void main()
{
char errbuf[PCAP_ERRBUF_SIZE] = { 0 };
pcap_if_t *alldevs = NULL;
if(pcap_findalldevs(&alldevs, errbuf) == -1)
return;
if((adhandle = pcap_open(alldevs->name, 0x10000, PCAP_OPENFLAG_PROMISCUOUS, 1000, NULL, errbuf)) == NULL)
return;
pcap_freealldevs(alldevs);
//udp
//send_udp_data(inet_addr("192.168.0.100"), 45630, inet_addr("192.168.0.100"), 6000, "hello", 6);
//tcp
int saddr = inet_addr("192.168.0.100");
int sport = 52803;
send_tcp_data(saddr, sport, inet_addr("202.104.205.75"), 80,
0, 0, tcp_flags(0, 0, 0, 0, 1, 0),
"", 0
);
Sleep(20);
send_tcp_data(saddr, sport, inet_addr("202.104.205.75"), 80,
1, 1, tcp_flags(0, 1, 0, 0, 0, 0),
"", 0
);
return;
}
//===========================================================================//
后记
虽然数据包组装了,也发送了, 还是出问题了.
因为当我发送一个数据包给服务器,服务器也给我发回来一个数据包.这时候系统收到数据包,发现这个端口占用..然后就发一个RST的TCP给服务器, 让服务器关闭这个连接 > <!!
Winpcap是没办法阻止系统收到/发送数据包的. 因而不能阻止RST.
下面是网上截下来TCP的说明(http://www.hackbase.com/tech/2011-04-26/63452.html):
大部分TCP/IP实现遵循以下原则:
1:当一个SYN或者FIN数据包到达一个关闭的端口,TCP丢弃数据包同时发送一个RST数据包。
2:当一个RST数据包到达一个监听端口,RST被丢弃。
3:当一个RST数据包到达一个关闭的端口,RST被丢弃。
4:当一个包含ACK的数据包到达一个监听端口时,数据包被丢弃,同时发送一个RST数据包。
5:当一个SYN位关闭的数据包到达一个监听端口时,数据包被丢弃。
6:当一个SYN数据包到达一个监听端口时,正常的三阶段握手继续,回答一个SYN|ACK数据包。
7:当一个FIN数据包到达一个监听端口时,数据包被丢弃。
如那篇文章所说,利用上述规则我们可以进行端口扫描(这方法总比遍历connect高效吧!),
但是伪造数据来刷票是不行了,除非自己能改操作系统...