TCP伪装
Posted Fireplusplus
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了TCP伪装相关的知识,希望对你有一定的参考价值。
什么是TCP伪装
TCP我们都知道,累积确认,丢包重传,拥塞控制等特性共同保障了它的可靠性。而如果抛弃这些特性,无需保障可靠,像UDP一样直接发送的TCP报文,便可认为是做了TCP伪装了。
为什么要做TCP伪装
运营商(ISP)通常做转发策略时,默认TCP包的优先级比UDP更高,也即发生网络拥堵时,优先丢弃非TCP的包。为了反制运营商的这种策略,让我们的非TCP报文也能够获更公平、更稳定的传输链路,便有了TCP伪装。毕竟你也不想你的视频卡帧吧-_-!
怎么做
linux提供了原始套接字的接口,通过此接口,我们可以收发原始数据包(带IP包头的)
int StartFakeTcp(const char *ip, short port)
// 打开原始套接字
int sock = socket(AF_INET, SOCK_RAW, IPPROTO_TCP);
if (sock < 0)
perror("socket");
return -1;
// 由应用层填充IP头
const int on =1;
if (setsockopt(sock, IPPROTO_IP, IP_HDRINCL, &on, sizeof(on)) < 0)
perror("setsockopt");
return -1;
struct sockaddr_in local;
SetAddr(ip, port, &local);
if (bind(sock, (struct sockaddr *)&local, sizeof(local)) < 0)
perror("bind");
return -1;
return sock;
从计算机网络分层的角度来看的话,普通socket只能够访问传输层以及传输层以上的数据,这是因为IP层将数据包传递给传输层时下层的数据包头已经被丢弃了。而原始套接字却没有这样做。因此它对上下层的数据都可以访问。所以使用 raw套接字你可以实现上至应用层的数据操作,也可以实现下至链路层的数据操作。
接下来所谓的伪装,其实就是IP和传输层的头部进行重新构造了。对于IP层,struct ip构建IP包头,传输层则可以通过struct tcphdr构建TCP头。
// 构建IP包头
int BuildIpPkt(char* data, int len, struct in_addr *ip_src, struct in_addr *ip_dst, uint8_t proto)
struct ip *ip_hdr = (struct ip*)(data - sizeof(struct ip));
memcpy(&ip_hdr->ip_src, ip_src, sizeof(*ip_src));
memcpy(&ip_hdr->ip_dst, ip_dst, sizeof(*ip_dst));
ip_hdr->ip_v = 4;
ip_hdr->ip_hl = sizeof(*ip_hdr) / 4;
ip_hdr->ip_len = htons(len + sizeof(*ip_hdr));
ip_hdr->ip_off = htons(0);
ip_hdr->ip_ttl = 0xff;
ip_hdr->ip_p = proto;
ip_hdr->ip_id = 0;
ip_hdr->ip_sum = htons(GetIpCheckSum((char*)ip_hdr, sizeof(*ip_hdr)));
return sizeof(struct ip) + len;
// 构建TCP包头
int BuildPkt(char* data, int len,
struct in_addr *ip_src, short port_src,
struct in_addr *ip_dst, short port_dst,
uint32_t seq, uint32_t ack_seq, uint8_t syn, uint8_t ack)
struct tcphdr *tcp_hdr = (struct tcphdr*)(data - sizeof(struct tcphdr));
tcp_hdr->doff = sizeof(*tcp_hdr) / 4;
tcp_hdr->source = htons(port_src);
tcp_hdr->dest = htons(port_dst);
tcp_hdr->seq = htonl(seq);
tcp_hdr->ack_seq = htonl(ack_seq);
if (syn) tcp_hdr->syn = 1;
if (ack) tcp_hdr->ack = 1;
tcp_hdr->th_sum = GetTcpCheckSum((char*)tcp_hdr, sizeof(*tcp_hdr) + len, ip_src, ip_dst);
return BuildIpPkt((char*)tcp_hdr, len + sizeof(struct tcphdr), ip_src, ip_dst, IPPROTO_TCP);
如此一来,我们就有了一个TCP报文,它长的跟正经的TCP报文没什么两样,但就是无连接。那么在运营商的策略看来,它的优先级就高了那么一些。
12:03:26.641749 lo In IP (tos 0x0, ttl 255, id 36489, offset 0, flags [none], proto TCP (6), length 40)
127.0.0.1.6667 > 127.0.0.1.6666: Flags [S], cksum 0x4d92 (correct), seq 12345, win 0, length 0
12:03:26.641793 lo In IP (tos 0x0, ttl 64, id 0, offset 0, flags [DF], proto TCP (6), length 44)
127.0.0.1.6666 > 127.0.0.1.6667: Flags [S.], cksum 0xfe20 (incorrect -> 0x238a), seq 292882122, ack 12346, win 65495, options [mss 65495], length 0
12:03:26.641797 lo In IP (tos 0x0, ttl 64, id 0, offset 0, flags [DF], proto TCP (6), length 40)
127.0.0.1.6667 > 127.0.0.1.6666: Flags [R], cksum 0x4d8f (correct), seq 12346, win 0, length 0
12:03:26.641896 lo In IP (tos 0x0, ttl 255, id 36490, offset 0, flags [none], proto TCP (6), length 40)
我这里实际验证时,server(6666)、client(6667)都在同一台机器上,client发syn、server回syn+ack都没有什么问题,第三次时,内核主动发了一个RST报文,这是由于kernel层没有半连接缓存导致的,我通过修改防火墙规则拦截了这个报文,避免假连接被中断:
iptables -I OUTPUT -p tcp --tcp-flags RST RST --dport 6666 -j DROP
但client的ack没有发送出去,这一点原因还待确认。
另外
部分防火墙设备在做转发时,会对数据包进行校验,如果识别出你的TCP包没有对应的连接跟踪,则认为是异常包,会直接丢弃掉。此时我们在真正发送数据之前,先模拟一轮TCP握手即可。
附
https://github.com/Fireplusplus/FakeTCP
以上是关于TCP伪装的主要内容,如果未能解决你的问题,请参考以下文章