基于开源tfifw的Windows传输层防火墙实现(实现用户态与内核态交互)
实验要求
- 基于开源的tdifw防火墙代码,实现一个基本的、能够在Windows XP环境下运行的传输层过滤防火墙功能,能够在DbgView等调试软件下看到调试效果。
- 基本要求:tdifw能够正确编译、安装、运行,基本理解tdifw的功能框架,能够使用DbgView等调试软件正确定位tdifw代码中的TCP数据传输。
- 功能要求:在满足基本要求的前提下,能够改写tdifw中关于TCP connect部分的代码,能够读取不同的目标/源IP地址(如:网站)、端口(不同的服务、应用),能够在DbgView中显示这些信息;更进一步,试着根据目标IP地址的不同,对特定IP地址、端口进行拦截;
- 测试网络连接时,可采用自主配置服务器(如web,ssh,ftp等)。
- 拔高:设计并编写一个用户态的交互配置程序。
实验参考
tdifw实验所需的环境、工具,及注意事项
- 参考资料《寒江独钓》,以下简称《寒》;
- 可进阶阅读《寒》第二章内容,了解内核程序的基本特点;
- 学习《寒》第十章内容,了解TDI驱动程序的基本构建,主要学习tdifw的编译方法和程序结构(可重点关注10.4,10.5);
- 基于《寒》第十章的tdifw代码(在tdi-noChinese目录下),编译tdifw驱动;注意,按照《寒》中的简化方法,绝大部分tdifw已经被封装到一个lib库里了(代码位于tdi_fw子目录),另一个子目录tdifw_smpl用来使用这个lib,生成最终的防火墙内核;
- 实验中所需的绝大部分功能位于disp_conn.c文件中,其中函数tdi_connect值得重点关注,这是实现TCP连接的功能函数,本实验所需的各类要求均可在该函数中完成。
- 实验时,也可以自行下载tdifw公开的源代码,而不采用《寒》中的lib封装代码,其原理、方法是一样的;
- 对于代码的阅读、改写、编辑,软件可根据自己的习惯自选用,WDK编译器一律采用命令行编译模式;
- 实验需要的工具:
必需工具:虚拟机工具(含XP镜像)、驱动开发包WDK、调试工具DbgView等(目录中均已包); 可选工具:内核服务开启工具、驱动加载工具等(目录中均已包含) - 需要自行学习WDK的编译方式(在本机上安装WDK开发包并进行编译、生成sys防火墙程序;在虚拟机上测试所生成的sys防火墙程序)、DbgView的调试方式(对于初学者,这是一个相对简单的工具)。
注意:WDK命令行环境下编译驱动程序,全路径名中不允许出现中文字符和空格!
实验资料
https://download.csdn.net/download/PlanetRT/20234142.
实验原理
基于开源的tdifw防火墙代码,实现一个基本的、在windowsXP环境下运行的传输层过滤防火墙功能。主要通过对tdifw防火墙代码中的disp_conn.c、filter.c等文件进行修改。 Tdifw过滤的主要原理是通过获取连接的请求(request),并将连接的各个属性加入到请求(request)中。通过调用过滤器过滤(quick_filter(&request, &rule)),将请求的各个属性放到过滤器(filter)的规则链(rule chain) 中进行匹配,当匹配成功时,返回匹配成功的规则的结果(result)。根据返回的结果(result)对该连接请求做出相应的操作(允许连接:FILTER_ALLOW,拒绝连接:FILTER_DENY)。 添加过滤规则的主要原理是通过向过滤器中添加规则来实现过滤。首先在filter.c文件中创建新的规则(声明一个规则结构体:struct flt_rule rlues),并将规则中的属性(例如:proto(协议)、result(结果)、direction(方向)、addr_from(源IP地址)、mask_from(源地址掩码)、port_from(源端口号)、port2_from(源端口范围)等)设定为想要过滤的连接的对应属性。然后将新的规则(rules)加入到过滤器(filter)的规则链中(add_flt_rule(chain,& flt_rule)),并将该规则链进行激活(activate_flt_chain(chain)),即可对目标连接进行过滤。当不需要过滤某一连接时,可将过滤器中对应的规则删除(clear_flt_chain(chain))。 过滤规则匹配的主要原理是首先判断请求(request)的IP地址是否合法,再判断过滤器(filter)的规则链(rule chain)是否激活。然后依次循环规则链(rule chain)中的规则。
内核驱动程序的编译安装此处不做演示。
规则匹配流程图
如下:
具体代码
静态添加规则
部分可能用到的宏定义:
#define DIRECTION_IN 0
#define DIRECTION_OUT 1
#define DIRECTION_ANY -1
#define RULE_LOG_NOLOG 0
#define RULE_LOG_LOG 1
#define RULE_LOG_COUNT 2
#define IPPROTO_ANY -1
#define IPPROTO_IP 0
#define IPPROTO_ICMP 1
#define IPPROTO_TCP 6
#define IPPROTO_UDP 17
enum {
FILTER_ALLOW = 1,
FILTER_DENY,
FILTER_PACKET_LOG,
FILTER_PACKET_BAD,
FILTER_DISCONNECT
};
#define CHECK_ADDR_PORT(r_addr_from, r_mask_from, r_port_from, r_port2_from, \
r_addr_to, r_mask_to, r_port_to, r_port2_to) \
((r_addr_from & r_mask_from) == (from->sin_addr.s_addr & r_mask_from) && \
(r_addr_to & r_mask_to) == (to->sin_addr.s_addr & r_mask_to) && \
(r_port_from == 0 || ((r_port2_from == 0) ? (r_port_from == from->sin_port) : \
(ntohs(from->sin_port) >= ntohs(r_port_from) && ntohs(from->sin_port) <= ntohs(r_port2_from)))) && \
(r_port_to == 0 || ((r_port2_to == 0) ? (r_port_to == to->sin_port) : \
(ntohs(to->sin_port) >= ntohs(r_port_to) && ntohs(to->sin_port) <= ntohs(r_port2_to))))) \
规则结构体(ipc.c):
struct flt_rule {
union {
struct flt_rule *next;
int chain;
};
int result;
int proto;
int direction;
ULONG addr_from;
ULONG mask_from;
USHORT port_from;
USHORT port2_from;
ULONG addr_to;
ULONG mask_to;
USHORT port_to;
USHORT port2_to;
int log;
UCHAR sid_mask[MAX_SIDS_COUNT / 8];
char rule_id[RULE_ID_SIZE];
};
struct flt_rule rlues1 = {
{0},
FILTER_DENY,
IPPROTO_TCP,
DIRECTION_OUT,
0,
65535,
0,
0,
18095020,
65535,
20480,
0,
RULE_LOG_LOG,
"1111111111111111",
"startup"
};
规则匹配(disp_conn.c):
result = quick_filter(&request, &rule);
if(result == FILTER_ALLOW){
KdPrint(("fuck %x \n",((const struct sockaddr_in *)&request.addr.from)->sin_addr.s_addr));
}
添加规则(filter.c):
NTSTATUS
add_flt_rule(int chain, const struct flt_rule *rule)
{
NTSTATUS status;
struct flt_rule *new_rule;
KIRQL irql;
if (chain < 0 || chain >= MAX_CHAINS_COUNT)
return STATUS_INVALID_PARAMETER_1;
KeAcquireSpinLock(&g_rules.guard, &irql);
new_rule = (struct flt_rule *)malloc_np(sizeof(struct flt_rule));
if (new_rule == NULL) {
KdPrint(("[tdi_fw] add_flt_rule: malloc_np\n"));
status = STATUS_INSUFFICIENT_RESOURCES;
goto done;
}
memcpy(new_rule, rule, sizeof(*new_rule));
new_rule->next = NULL;
if (g_rules.chain[chain].tail == NULL) {
g_rules.chain[chain].head = new_rule;
g_rules.chain[chain].tail = new_rule;
} else {
g_rules.chain[chain].tail->next = new_rule;
g_rules.chain[chain].tail = new_rule;
}
status = STATUS_SUCCESS;
KdPrint(("YES\n"));
done:
KeReleaseSpinLock(&g_rules.guard, irql);
return status;
}
add_flt_rule(0,&rlues1);
add_flt_rule(0,&rlues2);
activate_flt_chain(0);
如果不需要通过用户态与内核态通信添加,则只需在filter.c中的过滤器初始化函数filter_init中添加即可。 但这种方法比较固定,如果需要更新规则,则只能在代码中添新规则后重新编译安装。通过用户态与内核态的交互会在下边实现。
用户态与内核态交互添加
上边已经实现了静态添加规则的方法,这部分主要实现用户态与内核态交互的动态添加方式。 首先需要实现的是用户态与内核态的通信,具体原理及实现网上教程很多,此处不一一赘述。主要是通信后,内核态对用户态传入数据的处理和规则的添加。 本实验没有实现用户态的可视化界面,如果有需要可自行添加。 进程通信:
include <windows.h>
#include <stdio.h>
#include <winioctl.h>
#define MY_DVC_IN_CODE (ULONG)CTL_CODE(FILE_DEVICE_UNKNOWN,0xa01,METHOD_BUFFERED,FILE_READ_DATA|FILE_WRITE_DATA)
#define MY_DVC_OUT_CODE (ULONG)CTL_CODE(FILE_DEVICE_UNKNOWN,0xa00,METHOD_BUFFERED,FILE_READ_DATA|FILE_WRITE_DATA)
int main()
{
HANDLE device=CreateFile("\\\\.\\tdifw",
GENERIC_READ|GENERIC_WRITE,0,0,
OPEN_EXISTING,
FILE_ATTRIBUTE_SYSTEM,0);
if(device==INVALID_HANDLE_VALUE)
{
printf("CreateFile Fail\n");
}
else
{
printf("CreateFile Success\n");
}
char in_buffer[1000];
int in_buffer_len=strlen(in_buffer);
char out_buffer[1000];
int out_buffer_len=1000;
DWORD length=0;
printf("***********< Welcome to use XR's TDI_FW >***********\n");
printf("Tips:Input rules in the roder of whether allowed,protocol,\n");
printf("direction,source IP,source mask,source port,source port range,\n");
printf("destination IP,destination mask,destination port,destination port range.\n");
printf("Please input '*' after input an attribute\n");
scanf("%s",in_buffer);
in_buffer_len=strlen(in_buffer);
while(DeviceIoControl(device,
MY_DVC_IN_CODE,
in_buffer,
in_buffer_len,
out_buffer,
out_buffer_len,
&length,
NULL))
{
printf("Please input '*' after input an attribute\n");
scanf("%s",in_buffer);
in_buffer_len=strlen(in_buffer);
}
CloseHandle(device);
while(1) {}
return 0;
}
内核态处理(tdi_fw.c):
NTSTATUS MyDeviceIoControl(PDEVICE_OBJECT dev,
PIRP irp
)
{
INT ret;
PIO_STACK_LOCATION irpsp=IoGetCurrentIrpStackLocation(irp);
ULONG code=irpsp->Parameters.DeviceIoControl.IoControlCode;
ULONG in_len=irpsp->Parameters.DeviceIoControl.InputBufferLength;
ULONG out_len=irpsp->Parameters.DeviceIoControl.OutputBufferLength;
PVOID in_buffer=irp->AssociatedIrp.SystemBuffer;
PVOID out_buffer=irp->AssociatedIrp.SystemBuffer;
char *inbuffder=(char *)in_buffer;
if(irp->MdlAddress)
{
out_buffer = MmGetSystemAddressForMdlSafe(irp->MdlAddress, NormalPagePriority);
}
if(code==MY_DVC_IN_CODE)
{
temp=0;
num=0;
KdPrint(("in_buffer %s\n",in_buffer));
while(*inbuffder){
if(*inbuffder!='*'){
str[num] = *inbuffder;
nnum=(str[num]-'0')+nnum*10;
num++;
}
else if(*inbuffder=='*'){
temp++;
sum=nnum;
nnum=0;
KdPrint(("sum: %d\n",sum));
switch(temp){
case 1:rlues.result=sum;break;
case 2:rlues.proto=sum;break;
case 3:rlues.direction=sum;break;
case 4:rlues.addr_from=sum;break;
case 5:rlues.mask_from=sum;break;
case 6:rlues.port_from=sum;break;
case 7:rlues.port2_from=sum;break;
case 8:rlues.addr_to=sum;break;
case 9:rlues.mask_to=sum;break;
case 10:rlues.port_to=sum;break;
case 11:rlues.port2_to=sum;break;
}
num=0;
}
inbuffder++;
}
add_flt_rule(0,&rlues);
KdPrint(("add rlue successful! by XR \n"));
if(in_buffer&&out_buffer)
{
RtlCopyMemory(out_buffer, in_buffer, out_len);
}
KdPrint(("out_buffer %s\n",out_buffer));
irp->IoStatus.Information=out_len;
irp->IoStatus.Status=STATUS_SUCCESS;
}
else
{
irp->IoStatus.Information=0;
irp->IoStatus.Status=STATUS_SUCCESS;
}
IoCompleteRequest(irp,IO_NO_INCREMENT);
return irp->IoStatus.Status;
}
至此,所有有关代码均已贴出,如有兴趣,可查看实验资料中上传的全部代码(部分主要文件我加了详细注释)。
实验结果
静态添加
未启动驱动程序时,XP虚拟机(IP:10.0.2.15)对web服务器和ftp服务器的访问情况:
驱动程序启动后,XP虚拟机(IP:10.0.2.15)对web服务器和ftp服务器的访问情况:
用户态与内核态交互动态添加
加入规则前:
添加规则:
DbgView查看通信结果:
加入规则后:
总结
本实验为本人课内实验,如有错误,欢迎指正!
|