复仇者黑客组织

 找回密码
 立即注册

QQ登录

只需一步,快速开始

查看: 137|回复: 9

WFP网络过滤驱动——限制网站访问

[复制链接]

该用户从未签到

2

主题

2

帖子

8

积分

新手上路

Rank: 1

积分
8
发表于 2021-10-8 08:17:23 | 显示全部楼层 |阅读模式
[md]
0x1前言

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

TBC新兵交流-[md]0x1前言文中的注释有的来自微软官方的解释翻译,有的来自Windows内核安全与驱动开发书中的解释,也(1)
代码直接编译一大堆错误,网上找了找原因是环境配置问题
在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. // Legal values include:
  2. //    6.0  Available starting with Windows Vista RTM
  3. //    6.1  Available starting with Windows Vista SP1 / Windows Server 2008
  4. //    6.20 Available starting with Windows 7 / Windows Server 2008 R2
  5. //    6.30 Available starting with Windows 8 / Windows Server "8"
  6. #define FILTER_MAJOR_NDIS_VERSION   6
  7. #if defined(NDIS60)
  8. #define FILTER_MINOR_NDIS_VERSION   0
  9. #elseif defined(NDIS620)
  10. #define FILTER_MINOR_NDIS_VERSION   20
  11. #elseif defined(NDIS630)
  12. #define FILTER_MINOR_NDIS_VERSION   30
  13. #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. DriverObject->MajorFunction[IRP_MJ_CREATE] = WfpSampleIRPDispatch;
  2.                 DriverObject->MajorFunction[IRP_MJ_CLOSE] =  WfpSampleIRPDispatch;
  3.                 DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = WfpSampleIRPDispatch;
复制代码
CREATE与CLOSE在打开和关闭驱动时过滤函数会被调用
CONTROL用于与应用层交换
  1. switch( IrpStack->Parameters.DeviceIoControl.IoControlCode )
  2.                 {
  3.                 case IOCTL_WFP_SAMPLE_ADD_RULE:
  4.                         {
  5.                                 BOOLEAN bSucc = FALSE;
  6.                                 bSucc = AddNetRuleInfo(        pSystemBuffer ,uInLen );
  7.                                 if( bSucc == FALSE )
  8.                                 {
  9.                                         nStatus = STATUS_UNSUCCESSFUL;
  10.                                 }
  11.                                 break;
  12.                         }
  13.                 default:
  14.                         {
  15.                                 ulInformation = 0;
  16.                                 nStatus = STATUS_UNSUCCESSFUL;
  17.                         }
  18.                 }
  19.         }
复制代码
CONTROL码为IOCTL_WFP_SAMPLE_ADD_RULE时通过AddNetRuleInfo函数将数据加入链表
其他的IRP不做处理。
2创建设备对象与初始化
  1. UNICODE_STRING        uDeviceName = {0};
  2.         UNICODE_STRING        uSymbolName = {0};
  3.         PDEVICE_OBJECT        pDeviceObj = NULL;
  4.         NTSTATUS nStatsus = STATUS_UNSUCCESSFUL;
  5.         RtlInitUnicodeString(&uDeviceName,WFP_DEVICE_NAME);
  6.         RtlInitUnicodeString(&uSymbolName,WFP_SYM_LINK_NAME);
  7.         nStatsus = IoCreateDevice(DriverObject,0,&uDeviceName,FILE_DEVICE_UNKNOWN,0,FALSE,&pDeviceObj);
  8.         if( pDeviceObj != NULL )
  9.         {
  10.                 pDeviceObj->Flags |= DO_BUFFERED_IO;
  11.         }
  12.         IoCreateSymbolicLink(&uSymbolName,&uDeviceName);
  13.         return pDeviceObj;
复制代码
与普通驱动无区别,创建设备对象创建符号链接。
  1. InitializeListHead(&g_WfpRuleList);
  2.         KeInitializeSpinLock(&g_RuleLock);
  3.                 DriverObject->DriverUnload = DriverUnload;
复制代码
初始化链表用于存放数据,初始化锁用于保证安全写入链表。
3WFP功能函数
  1.            //注册呼出接口
  2.                 if (STATUS_SUCCESS  != WfpRegisterCallouts(g_pDeviceObj) )
  3.                 {
  4.                         break;
  5.                 }
  6.                 //添加呼出接口
  7.                 if( STATUS_SUCCESS != WfpAddCallouts() )
  8.                 {
  9.                         break;
  10.                 }
  11.                 //添加子层
  12.                 if( STATUS_SUCCESS != WfpAddSubLayer() )
  13.                 {
  14.                         break;
  15.                 }
  16.                 //添加过滤引擎
  17.                 if( STATUS_SUCCESS != WfpAddFilters() )
  18.                 {
  19.                         break;
  20.                 }
复制代码
流程正如注释,注册呼出接口->添加呼出接口->添加子层->添加过滤引擎;
接口实际上相当于普通驱动的过滤函数,接口设置的函数用于对数据进行处理,
子层是分层的一个更小单位,一个分层中包含了多个或零个子层,而子层之间的优先级由权重来区分,权重越大,优先级越高。
过滤引擎中
  1. Filter.action.calloutKey = WFP_SAMPLE_ESTABLISHED_CALLOUT_V4_GUID;
复制代码
这个值决定这个驱动加入那个层,
那层是什么,不同的层有什么区别。
TBC新兵交流-[md]0x1前言文中的注释有的来自微软官方的解释翻译,有的来自Windows内核安全与驱动开发书中的解释,也(2)
过滤引擎由很多层组成。每一层代表了网络协议栈的一个层。分层是一个容器,里面包含了0或多个过滤器,或一个或多个子层。每个分层都有一个唯一标识的UID,不同的层获取的数据不一样,这是Windows内核安全与驱动开发书中的解释
下面是我对层的理解
TBC新兵交流-[md]0x1前言文中的注释有的来自微软官方的解释翻译,有的来自Windows内核安全与驱动开发书中的解释,也(3)
在不同的层获取的数据不一样,这个不一样指的是首部,传输层比应用层多个tcp/udp首部,但本质上这个数据的用户数据部分没有改变,只是加了个首部,数据会经过每个层,我看书的时候以为获取不一样的数据是指不同的用户数据,看书的时候一直纠结,这些都能截获什么样的用户数据,看完了都没说,后搜了一下网络协议栈,才恍然大悟,其实用户数据会经过每个层,只是经过时会加上不同的头部,所以不同。所以不同的层就是有不同的首部。
其他的细节部分建议看书。
0x4开发日志

1打印过滤到的ip并进行拦截测试
  1. //ulRemoteIPAddress 表示远端IP
  2. ulRemoteIPAddress =
  3.     inFixedValues->
  4.     incomingValue[FWPS_FIELD_ALE_FLOW_ESTABLISHED_V4_IP_REMOTE_ADDRESS]
  5.     .value.uint32;
  6. DbgPrint("remote ip is %x\n", ulRemoteIPAddress);
复制代码
在过滤函数Wfp_Sample_Established_ClassifyFn_V4里对获取的ip进行打印
TBC新兵交流-[md]0x1前言文中的注释有的来自微软官方的解释翻译,有的来自Windows内核安全与驱动开发书中的解释,也(4)
百度ip为112.80.248.76,获取的数为0x7050f84c,转化一下刚好对应,ip获取成功。
  1.         if (ulRemoteIPAddress = 0x7050F84C)
  2.         {
  3.                 classifyOut->actionType = FWP_ACTION_BLOCK;
  4.         }
复制代码
先做个简单的拦截测试,classifyOut->actionType = FWP_ACTION_BLOCK;就是返回拦截的意思。
TBC新兵交流-[md]0x1前言文中的注释有的来自微软官方的解释翻译,有的来自Windows内核安全与驱动开发书中的解释,也(5)
百度刷新一下一直是没反应,就用cmd,ping一下baidu,拦截成功
2应用层获取网站输入并进行ip地址转化发给驱动

<div style="padding:15px 0;"><div style="font-size:12px;">[Asm] <em class="viewsource" style="cursor:pointer;font-size:12px;color:#369 !important;">纯文本查看</em> <em class="copycode" style="cursor:pointer;font-size:12px;color:#369 !important;">复制代码</em></div><pre class="brush: asm; gutter: true">
CString str;
GetDlgItemText(IDC_EDIT_PORT, str);
const char cstr;
char temp[100];
::wsprintfA(temp, "%ls", (LPCTSTR)str);
cstr = temp;
hostent
host = gethostbyname(cstr);
</pre></div>
实现定义变量str,从文本框中获取文本放入str,再把 char* 类型的str转为char类型,再通过字符串使用函数gethostbyname获取ip。
TBC新兵交流-[md]0x1前言文中的注释有的来自微软官方的解释翻译,有的来自Windows内核安全与驱动开发书中的解释,也(6)
然后我本来想测试一下IP是否成功get到,就用messagebox,显示一下host。host为0,调用WSAGetLastError,看一下没有符号表,就用od调了一下,返回值为0000276D,看了一下错误信息,函数就通过字符串获取ip,我还以为那个函数可以单独使用。又加上WSAStartup相关代码。
  1.         ULONG a[10] = { 0 };
  2.         int x = 0;
  3.         for (x;; x++)
  4.         {
  5.                  a[x] = inet_addr(inet_ntoa(*((in_addr *)host->h_addr_list[0])));
  6.                 if (host->h_addr_list[x] + host->h_length >= host->h_name)
  7.                 {
  8.                  break;
  9.                  }
  10.         }        
  11.         HANDLE hFile = CreateFile(_T("\\\\.\\wfp_sample_device"),GENERIC_ALL,0,NULL,OPEN_EXISTING,0,NULL);
  12.         if( hFile == INVALID_HANDLE_VALUE )
  13.         {
  14.                 return;
  15.         }
  16.         ST_WFP_NETINFO Info = {0};
  17.         // Info.m_uRemotePort = uPort;
  18.         for (x; x >= 0; x--)
  19.         {
  20.                 Info.m_ulRemoteIPAddr = a[x];
  21.                 DWORD dwNeedSize = 0;
  22.                 BOOL rValue = DeviceIoControl(hFile, IOCTL_WFP_SAMPLE_ADD_RULE, (LPVOID)&Info, sizeof(Info), NULL, 0, &dwNeedSize, NULL);
  23.         }
复制代码
接下来用个循环把通过函数inet_addr把字符串ip转成ULONG的值存入数组ULONG a[10],再打开驱动通过DeviceIoControl发送数据,for循环把数组每个值都发过去。
3驱动获取输入存入链表与过滤函数读取链表判断拦截
  1. BOOLEAN AddNetRuleInfo(PVOID pBuf,ULONG uLen)
  2. {
  3.         BOOLEAN bSucc = FALSE;
  4.         PST_WFP_NETINFO        pRuleInfo = NULL;
  5.         do
  6.         {
  7.                 PST_WFP_NETINFOLIST pRuleNode = NULL;
  8.                 KIRQL        OldIRQL = 0;
  9.                 pRuleInfo = (PST_WFP_NETINFO)pBuf;
  10.                 if( pRuleInfo == NULL )
  11.                 {
  12.                         break;
  13.                 }
  14.                 if( uLen < sizeof(ST_WFP_NETINFO) )
  15.                 {
  16.                         break;
  17.                 }
  18.                 pRuleNode = (PST_WFP_NETINFOLIST)ExAllocatePoolWithTag(NonPagedPool,sizeof(ST_WFP_NETINFOLIST),WFP_TAG);
  19.                 if( pRuleNode == NULL )
  20.                 {
  21.                         break;
  22.                 }
  23.                 memset(pRuleNode,0,sizeof(ST_WFP_NETINFOLIST));
  24.                 pRuleNode->m_stWfpNetInfo.m_uSrcPort = pRuleInfo->m_uSrcPort;
  25.                 pRuleNode->m_stWfpNetInfo.m_uRemotePort = pRuleInfo->m_uRemotePort;
  26.                 pRuleNode->m_stWfpNetInfo.m_ulSrcIPAddr = pRuleInfo->m_ulSrcIPAddr;
  27.                 pRuleNode->m_stWfpNetInfo.m_ulRemoteIPAddr = pRuleInfo->m_ulRemoteIPAddr;
  28.                 pRuleNode->m_stWfpNetInfo.m_ulNetWorkType = pRuleInfo->m_ulNetWorkType;
  29.                 pRuleNode->m_stWfpNetInfo.m_uDirection = pRuleInfo->m_uDirection;
  30.                 KeAcquireSpinLock(&g_RuleLock,&OldIRQL);
  31.                 InsertHeadList(&g_WfpRuleList,&pRuleNode->m_linkPointer);
  32.                 KeReleaseSpinLock(&g_RuleLock,OldIRQL);
  33.                 bSucc = TRUE;
  34.                 break;
  35.         }
  36.         while (FALSE);
  37.         return bSucc;
  38. }
复制代码
应用层通过DeviceIoControl发送数据,驱动通过设置DeviceIoControl的irp函数进行操作,在WfpSampleIRPDispatch函数里调用AddNetRuleInfo函数将数据存入全局变量的链表。
  1. BOOLEAN IsHitRule(USHORT ulRemoteIPAddress)
  2. {
  3.         BOOLEAN bIsHit = FALSE;
  4.         do
  5.         {
  6.                 KIRQL        OldIRQL = 0;
  7.                 PLIST_ENTRY        pEntry = NULL;
  8.                 if( g_WfpRuleList.Blink == NULL ||
  9.                         g_WfpRuleList.Flink == NULL )
  10.                 {
  11.                         break;
  12.                 }
  13.                 KeAcquireSpinLock(&g_RuleLock,&OldIRQL);
  14.                 pEntry = g_WfpRuleList.Flink;
  15.                 while( pEntry != &g_WfpRuleList )
  16.                 {
  17.                         PST_WFP_NETINFOLIST pInfo = CONTAINING_RECORD(pEntry,ST_WFP_NETINFOLIST,m_linkPointer);
  18.                         if (ulRemoteIPAddress == pInfo->m_stWfpNetInfo.m_ulRemoteIPAddr)
  19.                         {
  20.                                 bIsHit = TRUE;
  21.                                 break;
  22.                         }
  23.                         pEntry = pEntry->Flink;
  24.                 }
  25.                 KeReleaseSpinLock(&g_RuleLock,OldIRQL);
  26.         }
  27.         while (FALSE);
  28.         return bIsHit;
  29. }
复制代码
过滤函数Wfp_Sample_Established_ClassifyFn_V4里调用IsHitRule函数,传入参数ulRemoteIPAddress为当前包的ip,通过读取链表中从应用层获取的ip进行一一比对,有目标ip就把bIsHit = TRUE;,返回它,在外面判断bIsHit 的值,是true就拦截。
0x5调试日志

测试时发现没有拦截成功
应用层调试

TBC新兵交流-[md]0x1前言文中的注释有的来自微软官方的解释翻译,有的来自Windows内核安全与驱动开发书中的解释,也(7)
对wsprintfA下断点(其他的函数也一样),断下后返回用户代码,往下找inet_addr函数(ip转换函数)
TBC新兵交流-[md]0x1前言文中的注释有的来自微软官方的解释翻译,有的来自Windows内核安全与驱动开发书中的解释,也(8)
断下后第一次od里是112.80.248.75 而ping 的是112.80.248.76,我又重试了一次又是112.80.248.75 。
TBC新兵交流-[md]0x1前言文中的注释有的来自微软官方的解释翻译,有的来自Windows内核安全与驱动开发书中的解释,也(9)
然后我又全部重试了一次,这一次od里是112.80.248.76,而ping 的是112.80.248.75,我又ping一下又是112.80.248.76,猜测可能百度有多个ip(个人猜测,有错误欢迎大佬指出)。
应用层ip获取成功了,接下来看数据是否发送成功。
TBC新兵交流-[md]0x1前言文中的注释有的来自微软官方的解释翻译,有的来自Windows内核安全与驱动开发书中的解释,也(10)
  1. for (x; x >= 0; x--)
  2.         {
  3.                 ST_WFP_NETINFO Info = { 0 };
  4.                 Info.m_ulRemoteIPAddr = a[x];
  5.                 DWORD dwNeedSize = 0;
  6.                 BOOL rValue = DeviceIoControl(hFile, IOCTL_WFP_SAMPLE_ADD_RULE, (LPVOID)&Info, sizeof(Info), NULL, 0, &dwNeedSize, NULL);
  7.                 if (rValue == 0)
  8.                 {
  9.                         MessageBox(L"failed");
  10.                 }
  11.                 else
  12.                 {
  13.                         CString temp_value = _T("");
  14.                         temp_value.Format(_T("%x"), a[x]);
  15.                         MessageBox(temp_value);
  16.                 }
  17.         }
复制代码
a[x]是存放ip的ULONG数组,MessageBox(L"failed");是失败,MessageBox一个数值就是发送成功,数值为ip的值,4cf85070可以和ip对上,那就是没问题题,应用层的排查到此为止。
驱动层调试
  1. while( pEntry != &g_WfpRuleList )
  2.                 {
  3.                         PST_WFP_NETINFOLIST pInfo = CONTAINING_RECORD(pEntry,ST_WFP_NETINFOLIST,m_linkPointer);
  4.                         DbgPrint("111is --%x-\n", pInfo->m_stWfpNetInfo.m_ulRemoteIPAddr);
  5.                         char *ip1 = ulRemoteIPAddress;
  6.                         char *ip2 = pInfo->m_stWfpNetInfo.m_ulRemoteIPAddr;
  7.                         DbgBreakPoint();
  8.                         if (ulRemoteIPAddress == pInfo->m_stWfpNetInfo.m_ulRemoteIPAddr)
  9.                         {
  10.                                 bIsHit = TRUE;
  11.                                 break;
  12.                         }
  13.                         pEntry = pEntry->Flink;
  14.                 }602C9824
复制代码
在判断前加个DbgBreakPoint()断点,既可以看目标ip是否拦截到,也可以看应用层发的数据是否存入链表。
TBC新兵交流-[md]0x1前言文中的注释有的来自微软官方的解释翻译,有的来自Windows内核安全与驱动开发书中的解释,也(11)
从windbg中可以很明显的看出来了,ulRemoteIPAddress是0x24982c60
而pInfo->m_stWfpNetInfo.m_ulRemoteIPAddr是               0x602c9824,两个数刚好是反过来的,
总结一下:应用层的数据类型变来变去的过程里内存的存放翻转了一次,导致驱动进行比对时不相同,从而未能拦截。
0x6使用展示

TBC新兵交流-[md]0x1前言文中的注释有的来自微软官方的解释翻译,有的来自Windows内核安全与驱动开发书中的解释,也(12)
在网站框内输入想拦截的网址即可,第一次为未输入网站ping 百度成功,第二次为输入网站后ping百度,拦截成功,网址可输入多个。
0x7文件源码及总结

vs看源码方便些,就不把源码帖出来占篇幅了,直接给源码文件
链接:https://pan.baidu.com/s/1NTx0R3bL4zoLHP93tbQaig
提取码:1th8
解码码:123
总结的话,下次买电脑一定买个硬盘好的,虚拟机用着是真难受,开机要半天,点一下卡三秒。


[/md]

该用户从未签到

5

主题

4604

帖子

3503

积分

论坛元老

Rank: 8Rank: 8

积分
3503
发表于 2021-10-8 08:18:11 | 显示全部楼层
太厉害了,学习学习
回复

使用道具 举报

该用户从未签到

5

主题

4647

帖子

4636

积分

论坛元老

Rank: 8Rank: 8

积分
4636
发表于 2021-10-8 08:19:00 | 显示全部楼层
很强,先收藏了。。。。
回复

使用道具 举报

该用户从未签到

5

主题

4647

帖子

4636

积分

论坛元老

Rank: 8Rank: 8

积分
4636
发表于 2021-10-8 08:19:10 | 显示全部楼层
改了三遍了,markdown格式后面老是识别错误,现在还是识别错误:'(:'(
回复

使用道具 举报

该用户从未签到

5

主题

4647

帖子

4636

积分

论坛元老

Rank: 8Rank: 8

积分
4636
发表于 2021-10-8 08:19:57 | 显示全部楼层
编辑图片就要重新一个个的对,勉强能看了
回复

使用道具 举报

该用户从未签到

5

主题

4647

帖子

4636

积分

论坛元老

Rank: 8Rank: 8

积分
4636
发表于 2021-10-8 08:20:48 | 显示全部楼层
求问大佬这种情况怎么解决
TBC新兵交流-求问大佬这种情况怎么解决(13)
TBC新兵交流-求问大佬这种情况怎么解决(14)
回复

使用道具 举报

该用户从未签到

5

主题

4604

帖子

3503

积分

论坛元老

Rank: 8Rank: 8

积分
3503
发表于 2021-10-8 08:21:43 | 显示全部楼层
用wdm试试,或者按我发的环境配置
回复

使用道具 举报

该用户从未签到

2

主题

4595

帖子

3466

积分

论坛元老

Rank: 8Rank: 8

积分
3466
发表于 2021-10-8 08:21:57 | 显示全部楼层
已经解决啦原因是本机版本太高,使用的函数时FN3,但是程序里面的声明还是按照FN1的filter1来的,改成filter3就好了
回复

使用道具 举报

该用户从未签到

5

主题

4604

帖子

3503

积分

论坛元老

Rank: 8Rank: 8

积分
3503
发表于 2021-10-8 08:22:16 | 显示全部楼层
[C++]  
  1. VOID NTAPI Wfp_Sample_Established_ClassifyFn_V4(
  2.         IN const FWPS_INCOMING_VALUES  *inFixedValues,
  3.         IN const FWPS_INCOMING_METADATA_VALUES  *inMetaValues,
  4.         IN OUT VOID  *layerData,
  5.         IN OPTIONAL const void  *classifyContext,
  6.         IN const FWPS_FILTER1  *filter,
  7.         IN UINT64  flowContext,
  8.         OUT FWPS_CLASSIFY_OUT  *classifyOut
  9.         )
复制代码


按照宏定义来的话应该是FWPS_FILTER
回复

使用道具 举报

该用户从未签到

5

主题

4647

帖子

4636

积分

论坛元老

Rank: 8Rank: 8

积分
4636
发表于 2021-10-8 08:22:27 | 显示全部楼层
老师的作业之一:'(来学习一下
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

关闭Powered by ©科大讯飞语音云

QQ|Archiver|手机版|小黑屋|TBC ( 鄂ICP备19004742号(鄂ICP备19004742号-2) )|网站地图|鄂ICP备19004742号(鄂ICP备19004742号-2) 联系站长

GMT+8, 2021-10-16 07:22 , Processed in 0.859375 second(s), 62 queries .

Powered by TBC! X3.4

© 2001-2020 TBC.. 技术支持 by 复仇者黑客组织

快速回复 返回顶部 返回列表