首页
论坛
课程
招聘
[原创][原创]WFP网络过滤驱动——限制网站访问
2021-1-24 18:59 10940

[原创][原创]WFP网络过滤驱动——限制网站访问

2021-1-24 18:59
10940

0x1前言

文中的注释有的来自微软官方的解释翻译,有的来自Windows内核安全与驱动开发书中的解释,也有的来自我个人的理解。
代码功能是在Windows内核安全与驱动开发第15章中Wfpsample代码的基础上完成的.

0x2环境配置

 

代码直接编译一大堆错误,网上找了找原因是环境配置问题

 

在wfp开发是遇到

 

FwpmEngineOpen0无法解析;NET_BUFFER_LIST未定义

 

在链接器->输入 的附加依赖项中加入如下lib,自己项目需要什么就加什么

 

$(DDK_LIB_PATH)\NTOSKrnl.lib;$(DDK_LIB_PATH)\FwpKClnt.lib;$(DDK_LIB_PATH)\NetIO.lib;$(DDK_LIB_PATH)\NDIS.lib;$(DDK_LIB_PATH)\WDMSec.lib;$(SDK_LIB_PATH)\UUID.lib;

 

定义的NDIS和系统版本关系

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Legal values include:
//    6.0  Available starting with Windows Vista RTM
//    6.1  Available starting with Windows Vista SP1 / Windows Server 2008
//    6.20 Available starting with Windows 7 / Windows Server 2008 R2
//    6.30 Available starting with Windows 8 / Windows Server "8"
#define FILTER_MAJOR_NDIS_VERSION   6
 
#if defined(NDIS60)
#define FILTER_MINOR_NDIS_VERSION   0
#elseif defined(NDIS620)
#define FILTER_MINOR_NDIS_VERSION   20
#elseif defined(NDIS630)
#define FILTER_MINOR_NDIS_VERSION   30
#endif

NET_BUFFER_LIST未定义时,需要定义NDIS版本,可以参考ndis.h

 

在编译器的c/c++ ->预处理器中添加 NDIS61

 

https://blog.csdn.net/qq_41490873/article/details/109651615?spm=1001.2101.3001.4242

0x3WFP框架功能介绍

<u>以下为个人理解,有错误或不同的理解也欢迎指出</u>

 

初看wfp代码,有点乱,因为它既设置了呼出接口过滤函数,也设置了Irp过滤函数,仔细看完所有代码后发现,它并不像minifiter一样,有专门的新函数用于和应用层交互,虽然他也是一个新的框架相较于tdi和ndis,它仍然是用的Irp过滤函数与应用层交互,在与应用层DeivceIoControl这个irp交互时,会传入一个结构体,这个结构体里有各种想过滤的目标的信息,把这个结构体插入链表,然后在的注册的呼出接口过滤函数里,对该链表进行读取,然后判断是否是目标,从而进行相应的过滤操作。

1设置IRP过滤函数

1
2
3
DriverObject->MajorFunction[IRP_MJ_CREATE] = WfpSampleIRPDispatch;
        DriverObject->MajorFunction[IRP_MJ_CLOSE] =  WfpSampleIRPDispatch;
        DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = WfpSampleIRPDispatch;

CREATE与CLOSE在打开和关闭驱动时过滤函数会被调用

 

CONTROL用于与应用层交换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
switch( IrpStack->Parameters.DeviceIoControl.IoControlCode )
        {
        case IOCTL_WFP_SAMPLE_ADD_RULE:
            {
                BOOLEAN bSucc = FALSE;
                bSucc = AddNetRuleInfo(    pSystemBuffer ,uInLen );
                if( bSucc == FALSE )
                {
                    nStatus = STATUS_UNSUCCESSFUL;
                }
                break;
            }
        default:
            {
                ulInformation = 0;
                nStatus = STATUS_UNSUCCESSFUL;
            }
        }
    }

CONTROL码为IOCTL_WFP_SAMPLE_ADD_RULE时通过AddNetRuleInfo函数将数据加入链表

 

其他的IRP不做处理。

2创建设备对象与初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
UNICODE_STRING    uDeviceName = {0};
    UNICODE_STRING    uSymbolName = {0};
    PDEVICE_OBJECT    pDeviceObj = NULL;
    NTSTATUS nStatsus = STATUS_UNSUCCESSFUL;
    RtlInitUnicodeString(&uDeviceName,WFP_DEVICE_NAME);
    RtlInitUnicodeString(&uSymbolName,WFP_SYM_LINK_NAME);
    nStatsus = IoCreateDevice(DriverObject,0,&uDeviceName,FILE_DEVICE_UNKNOWN,0,FALSE,&pDeviceObj);
    if( pDeviceObj != NULL )
    {
        pDeviceObj->Flags |= DO_BUFFERED_IO;
    }
    IoCreateSymbolicLink(&uSymbolName,&uDeviceName);
    return pDeviceObj;

与普通驱动无区别,创建设备对象创建符号链接。

1
2
3
InitializeListHead(&g_WfpRuleList);
    KeInitializeSpinLock(&g_RuleLock);
        DriverObject->DriverUnload = DriverUnload;

初始化链表用于存放数据,初始化锁用于保证安全写入链表。

3WFP功能函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//注册呼出接口
 if (STATUS_SUCCESS  != WfpRegisterCallouts(g_pDeviceObj) )
 {
     break;
 }
 //添加呼出接口
 if( STATUS_SUCCESS != WfpAddCallouts() )
 {
     break;
 }
 //添加子层
 if( STATUS_SUCCESS != WfpAddSubLayer() )
 {
     break;
 }
 //添加过滤引擎
 if( STATUS_SUCCESS != WfpAddFilters() )
 {
     break;
 }

流程正如注释,注册呼出接口->添加呼出接口->添加子层->添加过滤引擎;

 

接口实际上相当于普通驱动的过滤函数,接口设置的函数用于对数据进行处理,

 

子层是分层的一个更小单位,一个分层中包含了多个或零个子层,而子层之间的优先级由权重来区分,权重越大,优先级越高。

 

过滤引擎中

1
Filter.action.calloutKey = WFP_SAMPLE_ESTABLISHED_CALLOUT_V4_GUID;

这个值决定这个驱动加入那个层,

 

那层是什么,不同的层有什么区别。

 

img

 

过滤引擎由很多层组成。每一层代表了网络协议栈的一个层。分层是一个容器,里面包含了0或多个过滤器,或一个或多个子层。每个分层都有一个唯一标识的UID,不同的层获取的数据不一样,这是Windows内核安全与驱动开发书中的解释

 

下面是我对层的理解

 

img

 

在不同的层获取的数据不一样,这个不一样指的是首部,传输层比应用层多个tcp/udp首部,但本质上这个数据的用户数据部分没有改变,只是加了个首部,数据会经过每个层,我看书的时候以为获取不一样的数据是指不同的用户数据,看书的时候一直纠结,这些都能截获什么样的用户数据,看完了都没说,后搜了一下网络协议栈,才恍然大悟,其实用户数据会经过每个层,只是经过时会加上不同的头部,所以不同。所以不同的层就是有不同的首部。

 

其他的细节部分建议看书。

0x4开发日志

1打印过滤到的ip并进行拦截测试

1
2
3
4
5
6
//ulRemoteIPAddress 表示远端IP
ulRemoteIPAddress =
    inFixedValues->
    incomingValue[FWPS_FIELD_ALE_FLOW_ESTABLISHED_V4_IP_REMOTE_ADDRESS]
    .value.uint32;
DbgPrint("remote ip is %x\n", ulRemoteIPAddress);

在过滤函数Wfp_Sample_Established_ClassifyFn_V4里对获取的ip进行打印

 

 

百度ip为112.80.248.76,获取的数为0x7050f84c,转化一下刚好对应,ip获取成功。

1
2
3
4
if (ulRemoteIPAddress = 0x7050F84C)
{
    classifyOut->actionType = FWP_ACTION_BLOCK;
}

先做个简单的拦截测试,classifyOut->actionType = FWP_ACTION_BLOCK;就是返回拦截的意思。

 

 

百度刷新一下一直是没反应,就用cmd,ping一下baidu,拦截成功

2应用层获取网站输入并进行ip地址转化发给驱动

1
2
3
4
5
6
7
CString str;
GetDlgItemText(IDC_EDIT_PORT, str);
const char* cstr;
char temp[100];
::wsprintfA(temp, "%ls", (LPCTSTR)str);
cstr = temp;
hostent *host = gethostbyname(cstr);

实现定义变量str,从文本框中获取文本放入str,再把 char* 类型的str转为char类型,再通过字符串使用函数gethostbyname获取ip。

 

 

然后我本来想测试一下IP是否成功get到,就用messagebox,显示一下host。host为0,调用WSAGetLastError,看一下没有符号表,就用od调了一下,返回值为0000276D,看了一下错误信息,函数就通过字符串获取ip,我还以为那个函数可以单独使用。又加上WSAStartup相关代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
ULONG a[10] = { 0 };
int x = 0;
for (x;; x++)
{
     a[x] = inet_addr(inet_ntoa(*((in_addr *)host->h_addr_list[0])));
    if (host->h_addr_list[x] + host->h_length >= host->h_name)
    {
     break;
     }
}   
HANDLE hFile = CreateFile(_T("\\\\.\\wfp_sample_device"),GENERIC_ALL,0,NULL,OPEN_EXISTING,0,NULL);
if( hFile == INVALID_HANDLE_VALUE )
{
    return;
}
ST_WFP_NETINFO Info = {0};
// Info.m_uRemotePort = uPort;
for (x; x >= 0; x--)
{
    Info.m_ulRemoteIPAddr = a[x];
    DWORD dwNeedSize = 0;
    BOOL rValue = DeviceIoControl(hFile, IOCTL_WFP_SAMPLE_ADD_RULE, (LPVOID)&Info, sizeof(Info), NULL, 0, &dwNeedSize, NULL);
 
}

接下来用个循环把通过函数inet_addr把字符串ip转成ULONG的值存入数组ULONG a[10],再打开驱动通过DeviceIoControl发送数据,for循环把数组每个值都发过去。

3驱动获取输入存入链表与过滤函数读取链表判断拦截

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
BOOLEAN AddNetRuleInfo(PVOID pBuf,ULONG uLen)
{
    BOOLEAN bSucc = FALSE;
    PST_WFP_NETINFO    pRuleInfo = NULL;
    do
    {
        PST_WFP_NETINFOLIST pRuleNode = NULL;
        KIRQL    OldIRQL = 0;
        pRuleInfo = (PST_WFP_NETINFO)pBuf;
        if( pRuleInfo == NULL )
        {
            break;
        }
        if( uLen < sizeof(ST_WFP_NETINFO) )
        {
            break;
        }
        pRuleNode = (PST_WFP_NETINFOLIST)ExAllocatePoolWithTag(NonPagedPool,sizeof(ST_WFP_NETINFOLIST),WFP_TAG);
        if( pRuleNode == NULL )
        {
            break;
        }
        memset(pRuleNode,0,sizeof(ST_WFP_NETINFOLIST));
        pRuleNode->m_stWfpNetInfo.m_uSrcPort = pRuleInfo->m_uSrcPort;
        pRuleNode->m_stWfpNetInfo.m_uRemotePort = pRuleInfo->m_uRemotePort;
        pRuleNode->m_stWfpNetInfo.m_ulSrcIPAddr = pRuleInfo->m_ulSrcIPAddr;
        pRuleNode->m_stWfpNetInfo.m_ulRemoteIPAddr = pRuleInfo->m_ulRemoteIPAddr;
        pRuleNode->m_stWfpNetInfo.m_ulNetWorkType = pRuleInfo->m_ulNetWorkType;
        pRuleNode->m_stWfpNetInfo.m_uDirection = pRuleInfo->m_uDirection;
        KeAcquireSpinLock(&g_RuleLock,&OldIRQL);
        InsertHeadList(&g_WfpRuleList,&pRuleNode->m_linkPointer);
        KeReleaseSpinLock(&g_RuleLock,OldIRQL);
        bSucc = TRUE;
        break;
    }
    while (FALSE);
    return bSucc;
}

应用层通过DeviceIoControl发送数据,驱动通过设置DeviceIoControl的irp函数进行操作,在WfpSampleIRPDispatch函数里调用AddNetRuleInfo函数将数据存入全局变量的链表。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
BOOLEAN IsHitRule(USHORT ulRemoteIPAddress)
{
    BOOLEAN bIsHit = FALSE;
    do
    {
 
        KIRQL    OldIRQL = 0;
        PLIST_ENTRY    pEntry = NULL;
        if( g_WfpRuleList.Blink == NULL ||
            g_WfpRuleList.Flink == NULL )
        {
            break;
        }
 
        KeAcquireSpinLock(&g_RuleLock,&OldIRQL);
        pEntry = g_WfpRuleList.Flink;
        while( pEntry != &g_WfpRuleList )
        {
            PST_WFP_NETINFOLIST pInfo = CONTAINING_RECORD(pEntry,ST_WFP_NETINFOLIST,m_linkPointer);
            if (ulRemoteIPAddress == pInfo->m_stWfpNetInfo.m_ulRemoteIPAddr)
            {
                bIsHit = TRUE;
                break;
            }
            pEntry = pEntry->Flink;
        }
        KeReleaseSpinLock(&g_RuleLock,OldIRQL);
    }
    while (FALSE);
    return bIsHit;
 
}

过滤函数Wfp_Sample_Established_ClassifyFn_V4里调用IsHitRule函数,传入参数ulRemoteIPAddress为当前包的ip,通过读取链表中从应用层获取的ip进行一一比对,有目标ip就把bIsHit = TRUE;,返回它,在外面判断bIsHit 的值,是true就拦截。

0x5调试日志

测试时发现没有拦截成功

应用层调试

 

对wsprintfA下断点(其他的函数也一样),断下后返回用户代码,往下找inet_addr函数(ip转换函数)

 

 

断下后第一次od里是112.80.248.75 而ping 的是112.80.248.76,我又重试了一次又是112.80.248.75 。

 

 

然后我又全部重试了一次,这一次od里是112.80.248.76,而ping 的是112.80.248.75,我又ping一下又是112.80.248.76,猜测可能百度有多个ip(个人猜测,有错误欢迎大佬指出)。

 

应用层ip获取成功了,接下来看数据是否发送成功。

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
for (x; x >= 0; x--)
    {
        ST_WFP_NETINFO Info = { 0 };
        Info.m_ulRemoteIPAddr = a[x];
        DWORD dwNeedSize = 0;
        BOOL rValue = DeviceIoControl(hFile, IOCTL_WFP_SAMPLE_ADD_RULE, (LPVOID)&Info, sizeof(Info), NULL, 0, &dwNeedSize, NULL);
        if (rValue == 0)
        {
            MessageBox(L"failed");
        }
        else
        {
            CString temp_value = _T("");
            temp_value.Format(_T("%x"), a[x]);
            MessageBox(temp_value);
        }
    }

a[x]是存放ip的ULONG数组,MessageBox(L"failed");是失败,MessageBox一个数值就是发送成功,数值为ip的值,4cf85070可以和ip对上,那就是没问题题,应用层的排查到此为止。

驱动层调试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
while( pEntry != &g_WfpRuleList )
        {
            PST_WFP_NETINFOLIST pInfo = CONTAINING_RECORD(pEntry,ST_WFP_NETINFOLIST,m_linkPointer);
            DbgPrint("111is --%x-\n", pInfo->m_stWfpNetInfo.m_ulRemoteIPAddr);
            char *ip1 = ulRemoteIPAddress;
            char *ip2 = pInfo->m_stWfpNetInfo.m_ulRemoteIPAddr;
 
            DbgBreakPoint();
 
            if (ulRemoteIPAddress == pInfo->m_stWfpNetInfo.m_ulRemoteIPAddr)
            {
 
                bIsHit = TRUE;
                break;
            }
            pEntry = pEntry->Flink;
        }602C9824

在判断前加个DbgBreakPoint()断点,既可以看目标ip是否拦截到,也可以看应用层发的数据是否存入链表。

 


从windbg中可以很明显的看出来了,ulRemoteIPAddress是0x24982c60

 

而pInfo->m_stWfpNetInfo.m_ulRemoteIPAddr是 0x602c9824,两个数刚好是反过来的,

 

总结一下:应用层的数据类型变来变去的过程里内存的存放翻转了一次,导致驱动进行比对时不相同,从而未能拦截。

0x6使用展示

 

在网站框内输入想拦截的网址即可,第一次为未输入网站ping 百度成功,第二次为输入网站后ping百度,拦截成功,网址可输入多个。

0x7文件源码及总结

vs看源码方便些,就不把源码帖出来占篇幅了,直接给源码文件

 

链接:https://pan.baidu.com/s/1NTx0R3bL4zoLHP93tbQaig
提取码:1th8
解码码:123

 

总结的话,下次买电脑一定买个硬盘好的,虚拟机用着是真难受,开机要半天,点一下卡三秒。


看雪2022 KCTF 秋季赛 防守篇规则,征题截止日期11月12日!(iPhone 14等你拿!)

最后于 2022-9-25 12:23 被下咯编辑 ,原因:
收藏
点赞5
打赏
分享
最新回复 (15)
雪    币: 33
活跃值: 活跃值 (258)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
gaybc 活跃值 2021-1-25 14:51
2
0
深信服VPN用的也是WPF。
hook可以绕过内外网隔离
雪    币: 4721
活跃值: 活跃值 (4876)
能力值: ( LV4,RANK:55 )
在线值:
发帖
回帖
粉丝
下咯 活跃值 2021-1-25 16:12
3
0
gaybc 深信服VPN用的也是WPF。 hook可以绕过内外网隔离
hook大法好,找对地方没什么是hook干不了的
雪    币: 12
活跃值: 活跃值 (199)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
蓝色的斗鱼 活跃值 2021-1-25 22:11
4
1
感谢分享,下一期来个拦截DNS解析的?
另外,虚拟机卡的话可以买个SSD,256G也就200多,把虚拟机数据都放SSD上保证飞起。
雪    币: 10107
活跃值: 活跃值 (10343)
能力值: ( LV12,RANK:240 )
在线值:
发帖
回帖
粉丝
pureGavin 活跃值 2 2021-1-26 08:40
5
0
感谢分享
雪    币: 4147
活跃值: 活跃值 (4396)
能力值: ( LV9,RANK:181 )
在线值:
发帖
回帖
粉丝
nevinhappy 活跃值 2 2021-1-26 16:55
6
0
感谢分享
雪    币: 20
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
wx_庸才 活跃值 2021-1-27 10:15
7
0
https://www.youtube.com/watch?v=BWm_w-otyjw&list=PLZ4EgN7ZCzJyUT-FmgHsW4e9BxfP-VMuo&index=18 WPF教程
雪    币: 2870
活跃值: 活跃值 (4394)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
舒默哦 活跃值 1 2021-1-27 11:04
8
0
感谢感谢
雪    币: 143
活跃值: 活跃值 (260)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
人在塔在 活跃值 2021-1-27 11:25
9
0
mark
雪    币: 224
活跃值: 活跃值 (1222)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
wx_0xC05StackOver 活跃值 2021-1-27 11:51
10
0
其实有个不错的方法是我之前用的,你注册回调中对于dns协议按照53端口进行拦截,对出去的dns包进行过滤,如果匹配到黑名单中的网址,直接drop这个包就可以了,这样做简单也不用考虑到收包高完成性级别irpl的问题,还要做重注入
雪    币: 135
活跃值: 活跃值 (120)
能力值: ( LV12,RANK:490 )
在线值:
发帖
回帖
粉丝
tgjarwl 活跃值 1 2021-1-27 14:27
11
0
直接加个wfp的filter规则就挺好
雪    币: 4721
活跃值: 活跃值 (4876)
能力值: ( LV4,RANK:55 )
在线值:
发帖
回帖
粉丝
下咯 活跃值 2021-1-27 14:45
12
0
wx_0xC05StackOver 其实有个不错的方法是我之前用的,你注册回调中对于dns协议按照53端口进行拦截,对出去的dns包进行过滤,如果匹配到黑名单中的网址,直接drop这个包就可以了,这样做简单也不用考虑到收包高完成性级别i ...
学到了
雪    币: 20
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
lzq123218 活跃值 2021-1-27 18:31
13
0
雪    币: 224
活跃值: 活跃值 (1222)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
wx_0xC05StackOver 活跃值 2021-1-29 16:14
14
0
gaybc 深信服VPN用的也是WPF。 hook可以绕过内外网隔离
vpn不老老实实改openvpn 用啥wfp啊 哈哈哈哈
雪    币: 256
活跃值: 活跃值 (299)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
十年后不是你 活跃值 2021-2-25 11:43
15
0
mark  就是win10 加载驱动 为什么老是要管理员权限  有没有不需要管理员权限的
雪    币: 1026
活跃值: 活跃值 (674)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
Caim Astraea 活跃值 2022-1-2 16:03
16
0
百度的IP其实挺多的,应该是用了CDN这种,没有利用域名拦截的做法么
游客
登录 | 注册 方可回帖
返回