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

### 使用 Winsock 实现 UDP 模拟 TCP 功能 为了使基于UDP的通信具有类似于TCP的行为,可以采用一些额外机制来模拟可靠性、顺序性和流控制等功能。下面介绍一种简单的方式,在此基础上还可以引入更多高级特性如拥塞控制。 #### 可靠性保障 由于UDP本身不提供数据包重传功能,因此可以在应用层加入简单的确认应答(ACK)机制。当发送方发出一段消息后等待接收方回应;若超时未收到,则重新发送该段直到成功为止。 ```cpp // 发送带有序列号的消息并等待ACK void SendWithAck(SOCKET sock, sockaddr_in* addr, char* msg, int seqNum){ // 添加序列号到消息头部... while(true){ sendto(sock,msg,strlen(msg),0,(SOCKADDR*)addr,sizeof(*addr)); fd_set fds; FD_ZERO(&fds); FD_SET(sock,&fds); struct timeval timeout={1,nullptr}; // 设置一秒超时 if(select((int)sock+1,&fds,nullptr,nullptr,&timeout)>0){ char ackBuf[2]; recvfrom(sock,ackBuf,sizeof(ackBuf)-1,0,nullptr,nullptr); if(/*解析出正确的ACK*/){ break; // 成功接收到对应seqNum的ACK则退出循环 } }else{ continue; // 超时重发 } } } ``` [^1] #### 数据分片与重组 对于超过MTU大小的数据块应当先分割成多个较小部分再逐个传递过去,并在另一侧按照原始次序拼合起来形成完整的信息体。 ```cpp #define MAX_FRAGMENT_SIZE 1472 // MTU减去IP头和UDP头长度后的最大载荷量级 struct FragmentHeader { uint16_t fragmentId : 13; // 片编号 bool isLastFragment : 1; // 是否最后一片标志位 }; // 对大数据进行切片操作 std::vector<std::string> SliceData(const std::string& data){ std::vector<std::string> fragments; size_t offset=0; do{ FragmentHeader header={ .fragmentId=(uint16_t)(fragments.size()), .isLastFragment=((data.length()-offset)<=MAX_FRAGMENT_SIZE) }; auto rawHeader=reinterpret_cast<char*>(&header); fragments.push_back(std::string(rawHeader)+data.substr(offset,std::min(MAX_FRAGMENT_SIZE,data.length()-offset))); offset+=MAX_FRAGMENT_SIZE; }while(offset<data.length()); return fragments; } // 接收端收集所有片段并将它们按正确顺序组合在一起 std::map<uint16_t,std::string> receivedFragments; std::mutex fragMutex; bool TryReassembleMessage(){ std::lock_guard<std::mutex> lock(fragMutex); if(receivedFragments.empty())return false; const auto firstIt=receivedFragments.begin(); const auto lastIt=receivedFragments.upper_bound(firstIt->first+(receivedFragments.size())); if(lastIt!=receivedFragments.end() && (
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Fireplusplus

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值