Skip to content
KitploitKITPLOIT
ToolsExploitsBlog
Log in
Submit
ToolsExploitsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

··Feeds·Contact·Privacy·© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
CVE-2023-21768 — Proof-of-concept exploit for CVE-2023-21768, a Windows Ancillary Function Driver (AFD.sys) arbitrary kernel write vulnerability enabling local privilege escalation via I/O ring. | Kitploit
Tools/GitHubGitHub/h1bana/cve-2023-21768
Privilege EscalationVulnerability AnalysisExploitationBinary Exploitation
GitHubh1bana/cve-2023-21768

CVE-2023-21768

Proof-of-concept exploit for CVE-2023-21768, a Windows Ancillary Function Driver (AFD.sys) arbitrary kernel write vulnerability enabling local privilege escalation via I/O ring.

View Repository
33 years agoNot yet reviewed

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share

CVE-2023-21768

Windows Ancillary Function Driver for WinSock

According to the detailed description of CVE-2023-21768 published by the Microsoft Security Response Center (MSRC), the vulnerability exists in the Ancillary Function Driver (AFD), whose system file is afd.sys. The AFD module is the kernel entry point for the WinSock API. In this analysis, I will use it to exploit privilege escalation on Windows 11.

Patch Diff and Root Cause Analysis

Download two versions of afd.sys from Winbindex: one version just before the patch, and one version after the patch. Then use Bindiff to compare these two versions. bindiff

Comparing the overall two versions, we see that only one function differs: AfdNotifyRemoveIoCompletion. Look at the details of the differences in this function between the two versions. bindiff

There are not many differences between the two versions. In the post-patch version, additional assembly instructions have been added to set parameters and call the ProbeForWrite function. According to Microsoft documentation, this function checks whether an address truly belongs to user-mode, has write permission, and is correctly aligned. Let's analyze this code in more detail:

  • pre-patch afd.sys version 10.0.22621.608 code1

  • post-patch afd.sys version 10.0.22621.1105 code2

Both check the value of r15_1; if it is 0, write the value of var_304 to the pointer specified in a field of struct_1. If it is non-zero, ProbeForWrite is called to ensure the pointer points to a valid address. In the pre-patch version, the value at var_304 is then written to the pointer, but this check is missing. From this patch, we can guess that we can call this code with a controlled value of arg3_1->field_18. If we can set a kernel address at field_18, we can write var_304 to a kernel memory address.

=> bug type: arbitrary kernel Write-Where

Now we need to find a way to trigger the bug. The function AfdNotifyRemoveIoCompletion is called directly in the function AfdNotifySock. crossRef

Similarly, searching for cross references of AfdNotifySock shows that it is not called directly from any other function, but the function address is stored at an address in .rdata cross2

This address is located just before AfdIrpCallDispatch. cross3

To trigger the bug, I will call DeviceIoControl with IOCTL_AFD_NOTIFY_SOCK, and AfdNotifySock will be called.

BOOL DeviceIoControl(
  [in]                HANDLE       hDevice,
  [in]                DWORD        dwIoControlCode,
  [in, optional]      LPVOID       lpInBuffer,
  [in]                DWORD        nInBufferSize,
  [out, optional]     LPVOID       lpOutBuffer,
  [in]                DWORD        nOutBufferSize,
  [out, optional]     LPDWORD      lpBytesReturned,
  [in, out, optional] LPOVERLAPPED lpOverlapped
);

reverse and debug

For each driver, a DRIVER_OBJECT object is created in the kernel, defined as follows:

typedef struct _DRIVER_OBJECT {
  CSHORT             Type;
  CSHORT             Size;
  PDEVICE_OBJECT     DeviceObject;
  ULONG              Flags;
  PVOID              DriverStart;
  ULONG              DriverSize;
  PVOID              DriverSection;
  PDRIVER_EXTENSION  DriverExtension;
  UNICODE_STRING     DriverName;
  PUNICODE_STRING    HardwareDatabase;
  PFAST_IO_DISPATCH  FastIoDispatch;
  PDRIVER_INITIALIZE DriverInit;
  PDRIVER_STARTIO    DriverStartIo;
  PDRIVER_UNLOAD     DriverUnload;
  PDRIVER_DISPATCH   MajorFunction[IRP_MJ_MAXIMUM_FUNCTION + 1];
} DRIVER_OBJECT, *PDRIVER_OBJECT;

The last member MajorFunction is an array of the driver's dispatch functions to handle communication between kernel and usermode. The dispatch function corresponding to calling DeviceIoControl is stored at MajorFunction[IRP_MJ_DEVICE_CONTROL].

#define IRP_MJ_DEVICE_CONTROL           0x0e

From the DriverEntry function of afd.sys, we can see that the driver created the device object "\Device\Afd": code3

It sets MajorFunction[IRP_MJ_DEVICE_CONTROL] = AfdDispatchDeviceControl, so when calling DeviceIoControl to communicate with the kernel, this function is called. code4

In AFD, there are two dispatch tables: AfdIrpCallDispatch and AfdImmediateCallDispatch. dispatchtable1 dispatchtable2

It is easy to see that AfdDispatchDeviceIoControl computes the subscript via the IoControlCode and retrieves the corresponding value from AfdIoctlTable to verify against the IoControlCode. 1

From the distance between the start address of AfdImmediateCallDispatch and the address storing AfdNotifySock, we calculate the index as 73, with control code 0x12127 ioctl

int main() {
    WSADATA WSAData;
    SOCKET s;
    SOCKADDR_IN sa;
    int ierr;

    WSAStartup(0x2, &WSAData);
    s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    memset(&sa, 0, sizeof(sa));
    sa.sin_port = htons(135);
    sa.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
    sa.sin_family = AF_INET;
    ierr = connect(s, (const struct sockaddr*)&sa, sizeof(sa));

    char outBuf[100];
    DWORD bytesRet;
    DWORD inbuf1[100];

    memset(inbuf1, 0, sizeof(inbuf1));

    DeviceIoControl((HANDLE)s, 0x12127, (LPVOID)inbuf1, 0x30, outBuf, 0, &bytesRet, NULL);
    return 0;
}

it works!

bp1

As mentioned earlier, the vulnerability occurs when we can pass an unvalidated pointer through a struct. This struct is passed directly from usermode via lpInBuffer of DeviceIoControl. It is then passed to AfdNotifySock as the 4th parameter and then to AfdNotifyRemoveIoCompletion as the 3rd parameter.

para1 para2 para3

Download Tool