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"
|
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;
|
这个值决定这个驱动加入那个层,
那层是什么,不同的层有什么区别。
过滤引擎由很多层组成。每一层代表了网络协议栈的一个层。分层是一个容器,里面包含了0或多个过滤器,或一个或多个子层。每个分层都有一个唯一标识的UID,不同的层获取的数据不一样,这是Windows内核安全与驱动开发书中的解释
下面是我对层的理解
在不同的层获取的数据不一样,这个不一样指的是首部,传输层比应用层多个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
总结的话,下次买电脑一定买个硬盘好的,虚拟机用着是真难受,开机要半天,点一下卡三秒。
[培训]二进制漏洞攻防(第3期);满10人开班;模糊测试与工具使用二次开发;网络协议漏洞挖掘;Linux内核漏洞挖掘与利用;AOSP漏洞挖掘与利用;代码审计。
最后于 2022-9-25 12:23
被zx_838741编辑
,原因: