references:
约定,本文所有的数字如果没有人特别说明,均为16进制
漏洞函数code path定位
afd.sys中的AfdNotifyRemoveIoCompletion函数的patch前后对比,左侧为patch过的,右侧为漏洞版本

可以看到,patch过后的代码多了一个对a1的判断,这个a1其实是previousMode,如果是1(用户模式),则对要写入的内存执行ProbeForWrite,这个函数会检查目标地址是否是用户模式地址空间,也就是防止用户模式进程直接写入内核的内存
a1是previousMode很好判断,查看caller函数AfdNotifySock的代码可知a1其实就是AfdNotifySock函数的a3

NTSTATUS ObReferenceObjectByHandle(
[in] HANDLE Handle,
[in] ACCESS_MASK DesiredAccess,
[in, optional] POBJECT_TYPE ObjectType,
[in] KPROCESSOR_MODE AccessMode,
[out] PVOID *Object,
[out, optional] POBJECT_HANDLE_INFORMATION HandleInformation
);
根据文档,ObReferenceObjectByHandle函数的第四个参数是AccessMode,因此就可以判断出AfdNotifyRemoveIoCompletion的a1是PreviousMode
查看AfdNotifySock的交叉引用,可以看到他在AfdImmediateCallDispatch派遣函数表的最后面,再往下就是另一张派遣函数表了

查看AfdImmediateCallDispatch的交叉引用,可以找到他在AfdFastIoDeviceControl函数的797行被用到

其中v64会作为这张表的index来获取目标函数,v64由a7计算出来
查看AfdFastIoDeviceControl函数的交叉引用,可以看到他在AfdFastIoDispatch派遣函数表中,再查看AfdFastIoDispatch的交叉引用

可以看到afd注册了FastIoDispatch
typedef struct _FAST_IO_DISPATCH {
ULONG SizeOfFastIoDispatch;
PFAST_IO_CHECK_IF_POSSIBLE FastIoCheckIfPossible;
PFAST_IO_READ FastIoRead;
PFAST_IO_WRITE FastIoWrite;
PFAST_IO_QUERY_BASIC_INFO FastIoQueryBasicInfo;
PFAST_IO_QUERY_STANDARD_INFO FastIoQueryStandardInfo;
PFAST_IO_LOCK FastIoLock;
PFAST_IO_UNLOCK_SINGLE FastIoUnlockSingle;
PFAST_IO_UNLOCK_ALL FastIoUnlockAll;
PFAST_IO_UNLOCK_ALL_BY_KEY FastIoUnlockAllByKey;
PFAST_IO_DEVICE_CONTROL FastIoDeviceControl;
PFAST_IO_ACQUIRE_FILE AcquireFileForNtCreateSection;
PFAST_IO_RELEASE_FILE ReleaseFileForNtCreateSection;
PFAST_IO_DETACH_DEVICE FastIoDetachDevice;
PFAST_IO_QUERY_NETWORK_OPEN_INFO FastIoQueryNetworkOpenInfo;
PFAST_IO_ACQUIRE_FOR_MOD_WRITE AcquireForModWrite;
PFAST_IO_MDL_READ MdlRead;
PFAST_IO_MDL_READ_COMPLETE MdlReadComplete;
PFAST_IO_PREPARE_MDL_WRITE PrepareMdlWrite;
PFAST_IO_MDL_WRITE_COMPLETE MdlWriteComplete;
PFAST_IO_READ_COMPRESSED FastIoReadCompressed;
PFAST_IO_WRITE_COMPRESSED FastIoWriteCompressed;
PFAST_IO_MDL_READ_COMPLETE_COMPRESSED MdlReadCompleteCompressed;
PFAST_IO_MDL_WRITE_COMPLETE_COMPRESSED MdlWriteCompleteCompressed;
PFAST_IO_QUERY_OPEN FastIoQueryOpen;
PFAST_IO_RELEASE_FOR_MOD_WRITE ReleaseForModWrite;
PFAST_IO_ACQUIRE_FOR_CCFLUSH AcquireForCcFlush;
PFAST_IO_RELEASE_FOR_CCFLUSH ReleaseForCcFlush;
} FAST_IO_DISPATCH, *PFAST_IO_DISPATCH;
那么AfdFastIoDeviceControl函数的类型就是PFAST_IO_DEVICE_CONTROL,根据WRK源代码可知该函数的签名如下
typedef
BOOLEAN
(*PFAST_IO_DEVICE_CONTROL) (
IN struct _FILE_OBJECT *FileObject,
IN BOOLEAN Wait,
IN PVOID InputBuffer OPTIONAL,
IN ULONG InputBufferLength,
OUT PVOID OutputBuffer OPTIONAL,
IN ULONG OutputBufferLength,
IN ULONG IoControlCode,
OUT PIO_STATUS_BLOCK IoStatus,
IN struct _DEVICE_OBJECT *DeviceObject
);
a7正是IoControlCode
使用AfdNotifySock函数在AfdImmediateCallDispatch派遣表的地址减去这张表的起始地址,再除以8,即可得到v64的值49
那么AfdIoctlTable[v64]就是AfdIoctlTable[49]
0: kd> dds AfdIoctlTable+4*49 l1
fffff806`2c850b24 00012127
这样我们就可以使用IoControlCode 12127来抵达漏洞函数
exp代码分析
使用漏洞控制IO ring结构体里面的regbuffer地址为用户可分配的用户模式地址,并控制regcount