13 minutes
Bring Your Own Trusted Caller (BYOTC): A New Way to Exploit Vulnerable Windows Drivers (Part 1)

Bring Your Own Vulnerable Driver (BYOVD) is a familiar Windows attack technique: an attacker loads a legitimate, signed driver and abuses a flaw in it to gain kernel-level capabilities. Microsoft maintains a vulnerable-driver blocklist to make this harder, while the community-maintained LOLDrivers project catalogs known vulnerable and malicious Windows drivers.
But sometimes the dangerous capability is intentional. A security or system-inspection driver may legitimately need to terminate processes, open privileged handles, or resist interference. Removing that functionality would break the product, so the driver must instead decide who is allowed to request it. Vendors therefore add caller authentication:
untrusted process ──X──> privileged driver operation
trusted client ───────> privileged driver operation
This creates a second security boundary. The question is no longer only whether an untrusted executable can send a privileged IOCTL, but whether an attacker can take control of the trusted caller:
- Can code be injected into the trusted process?
- Can an attacker launch the trusted image while retaining a powerful creator handle?
- Can trust be inherited across processes, or remain valid after the process has been modified?
I call this pattern Bring Your Own Trusted Caller (BYOTC): instead of attacking the kernel interface directly, obtain code execution inside—or otherwise abuse—the user-mode program the driver already trusts, then make it invoke the privileged interface. The authorization check succeeds, but its security meaning has been lost. BYOTC is therefore a confused-deputy problem at the user/kernel boundary.
This post develops the idea through two case studies from my research: Malwarebytes’ mbamchameleon.sys and System Informer’s SystemInformer.sys. Their trust models differ substantially, but both illustrate the same lesson:
Establishing—and continuously preserving—the integrity of a process at runtime is hard.
Case study 1: Malwarebytes Chameleon
In June 2025, I reported a BYOTC issue in Malwarebytes’ mbamchameleon.sys (SHA-256: d86b9b20788b6bff70a1a4c4111b2ea33b9ec705cc6b8fe869362fc3899820a3). The driver exposed an IOCTL, 0x222024, that ultimately invoked ZwTerminateProcess. This was not a stray memory-safety bug: process termination was a feature of the driver.
The driver did not allow every process to use that operation. A caller first had to register as trusted through IOCTL 0x222008. My reverse engineering showed that this path checked whether the calling process’s image was signed by Malwarebytes.
The problem was that a valid signature describes the file that started the process; it does not prove that the process is still executing only Malwarebytes-controlled code.
For the proof of concept, I launched the legitimate, signed MBAM.exe, injected a DLL into it, registered that process as trusted, and then asked the driver to terminate Microsoft Defender’s MsMpEng.exe:
attacker (administrator)
│
├── inject DLL ──> signed MBAM.exe
│ │
│ ├── IOCTL 0x222008: register trusted client
│ └── IOCTL 0x222024: terminate MsMpEng.exe
│
└────────────────> mbamchameleon.sys ──> ZwTerminateProcess
The proof of concept loads mbamchameleon.sys, injects into the signed Malwarebytes client, and uses the trusted process to terminate Microsoft Defender.
This case is the simplest form of BYOTC:
- Bring the signed driver.
- Bring its signed user-mode client.
- Gain execution inside the client.
- Let the client pass the driver’s signer check.
- Invoke the driver’s intended privileged feature for an unintended purpose.
Malwarebytes disclosure timeline
- June 15, 2025: I submit my Chameleon report to Malwarebytes.
- July 2, 2025: Malwarebytes says my report duplicates an earlier report made by a different researcher.
- September 5, 2025: My report is closed as a duplicate.
- October 31, 2025: Malwarebytes says a fix is in progress and that I will be invited to retest after release.
- March 4, 2026: 180 days have elapsed since the report was closed.
- September 3, 2026: I publicly disclose my findings in this blog post. As of publication, no fix is available.
I do not blame Malwarebytes for not shipping a fix after a year. As we will see in the next case study, even systematic validation of the client process can still leave a gap.
Case study 2: System Informer and the evil parent
System Informer was the more interesting target because KSystemInformer already performed unusually thorough client validation. It assigned each client one of five cumulative process-integrity levels. The state definitions and KphGetProcessState show exactly what each level added:
| Level | Checks required |
|---|---|
MINIMUM |
The process has a real backing file and section; the file is not writable in a way that permits replacement; the mapped image has no user-writable references; and the file is not participating in a transaction. |
LOW |
Everything in MINIMUM, plus a verified image. KphVerifyFileObject hashes the image with SHA-512 and verifies its adjacent .sig file using an RSA public key embedded in the driver. |
MEDIUM |
Everything in LOW, plus active driver-enforced process protection. Object Manager callbacks strip dangerous rights from handles opened to the protected process and its threads. |
HIGH |
Everything in MEDIUM, plus no untrusted image loads and no active debugger. |
MAXIMUM |
Everything in HIGH, plus proof that the process was securely created by a process that is HIGH. |
System Informer uses its own message system for user-kernel communication, and each message declares the minimum integrity level required to execute it. On every request, KphpCommsMessageNotifyCallback recomputes the caller’s state and rejects the message if the process does not meet that requirement. For example, KphMsgTerminateProcess requires MAXIMUM, so only a client at that level can ask the driver to terminate a process.
This reduced my research question to two parts: How does SystemInformer.exe obtain MAXIMUM, and can an attacker get code execution inside that instance? The answer to the first part lies in how System Informer starts.
How The Truest Client reached MAXIMUM
Before following the startup sequence, it helps to see the complete set of checks required for a MAXIMUM process:
| Protection | What the driver checks or enforces |
|---|---|
| Real backing image | The process has a real image file and section rather than an anonymous or substituted mapping. |
| File cannot be replaced | The image file is not writable in a way that would allow its contents to be changed after verification. |
| No writable image references | The mapped image section has no user-writable references. |
| No file transaction | The image file is not participating in a transaction that could present different contents at different times. |
| Cryptographic image verification | KphVerifyFileObject hashes the executable with SHA-512 and verifies its adjacent .sig file using an RSA public key embedded in the driver. |
| Protected process and thread handles | KphApplyObProtections uses Object Manager callbacks to strip dangerous rights from handles opened by outside processes. |
| No untrusted image loads | The image-load path tries to remove untrusted mappings and records any that remain. |
| Not being debugged | The driver checks whether the process is being debugged. |
| Secure creation history | The driver tracks whether the process was created through an approved parent-child chain. |
System Informer used three separate processes rather than gradually raising the trust level of one process. The sequence began when the user launched SystemInformer.exe with administrator rights. This first instance brought up the driver: KphConnect first tried to connect to an existing driver and, if none was available, created the kernel-driver service before starting it and connecting again.
1. The user launches SystemInformer.exe as administrator.
This first instance loads SystemInformer.sys and connects to it.
Because the driver started after this process was created, it can
verify the image but cannot establish the full creation-time chain. → LOW
2. Because it is below HIGH, it starts SystemInformer.exe -kh.
The driver observes this child from creation, verifies it,
protects it against dangerous outside handles, and confirms
that it is not being debugged and has loaded no untrusted images. → HIGH
3. Because the child is HIGH but not MAXIMUM, it starts
SystemInformer.exe -kh -kx. Under the old rule, creation by a
HIGH parent set SecurelyCreated; the child repeats all checks. → MAXIMUM
4. The MAXIMUM instance can invoke the most privileged driver APIs.
The user-mode restart decisions are in KsiConnectDriver, while the old HIGH parent → SecurelyCreated child rule is in KphpPerformProcessTracking.
How the evil-parent attack broke the chain
The gap was a narrow exception in an otherwise strong design. Windows process creation requires the creator to retain powerful handles while initializing its child. System Informer’s object callbacks normally stripped dangerous rights from handles to protected clients, but KphpShouldPermitCreatorProcess permitted the actual creator extra rights, including PROCESS_VM_OPERATION and PROCESS_VM_WRITE. Duplication was blocked, but the creator could use its own handle directly.
An elevated malicious.exe could therefore create the legitimate -kh instance and modify it through the creator handle after it became HIGH. That compromised process could create the -kx instance, which the old rule promoted to MAXIMUM, and modify that child through the next creator handle. All image and runtime checks could pass even though control flowed from the evil parent into the trusted chain.
malicious.exe
└─ creates and modifies ─> verified HIGH instance
└─ creates and modifies ─> verified MAXIMUM instance
└─> privileged driver APIs
Simply removing the creator exception was not practical: process initialization legitimately needed those rights and failed without them. The weakness was the combination of necessary Windows creation semantics with a trust rule that allowed a child to become more trusted than the process that still controlled its initialization.
How System Informer fixed it
The System Informer developers responded quickly, confirmed the gap, and designed a fix. The fix has two complementary parts.
First, the driver no longer treats a HIGH System Informer process as securely created merely because another HIGH process created it. The new process can advance into the maximum-trust chain only if its creator is already MAXIMUM, or if the creator is a protected process signed at Windows TCB/System level. This change is visible in KphpPerformProcessTracking, where the driver checks PsGetProcessProtection and accepts only PsProtectedSignerWinTcb or PsProtectedSignerWinSystem as the root of a new secure-creation chain.
Second, System Informer changed how it restarts itself. Rather than letting the weaker GUI process directly create the stronger instance, it creates a temporary service and asks the service path to perform the creation. The Service Control Manager runs services.exe as a WinTcb-protected process, providing an operating-system-backed root of trust. The user-mode side of this design is shown in the kph restart self through tcb commit.
The resulting chain is:
System Informer (< MAXIMUM)
│ create and start temporary service
▼
services.exe (WinTcb PPL)
│ create
▼
System Informer (HIGH)
│ create
▼
System Informer (MAXIMUM)
Because the untrusted initiator does not receive the privileged creator handle to the trusted child, and handle duplication across the verified-process boundary is already prohibited, the evil-parent path is broken. A malicious ordinary administrator cannot manufacture the WinTcb-protected root that the driver now requires.
Two nearby hardening changes also made relevant state checks “sticky”: once a violation such as debugging or an untrusted image load is observed, temporarily removing that condition cannot restore the process’s earlier trust. That addresses the broader TOCTOU concern: trust should generally be monotonic in the direction of revocation.
System Informer disclosure timeline
- April 20, 2025: I privately report the evil-parent design gap.
- April 20–21, 2025: System Informer’s maintainers author the TCB-rooted secure-creation and service-restart changes during our technical discussion.
- February 1, 2026: The rebased equivalents of those changes enter the current public repository history.
- August 29, 2026: The maintainer confirms that the updated signed driver has shipped to the Release Channel and closes the disclosure loop.
A lesson from game anti-cheat
Game anti-cheat provides a useful comparison. Kernel anti-cheat drivers also try to determine whether a user-mode process remains trustworthy while an adversary actively attempts to modify or deceive it. Riot Games’ Vanguard architecture overview describes its driver as validating memory and system state and checking that the client has not been tampered with.
Anti-cheat and general-purpose utilities have different compatibility constraints, but the structural problem is the same. The never-ending contest between anti-cheat developers and cheat authors shows that runtime integrity is not a single check that can be perfected. A live process can be influenced through its memory, threads, loaded modules, handles, creation history, and even its view of the operating system. BYOTC brings this lesson to driver review: a recognized process is not a static identity, but a security-sensitive object whose integrity must be established and maintained over time.
How should BYOTC be fixed?
The strongest pattern I have found so far is to anchor trust in Protected Process Light (PPL). Ideally, the trusted client itself runs as PPL. A less direct design, like System Informer’s fix, roots the client’s creation chain in a PPL process and combines that origin with extensive runtime protections. In both designs, Windows—not the vendor’s driver alone—protects the root of trust from an ordinary administrator.
That is much harder to deploy than it sounds. Windows has a supported route for protected antimalware services, but it is not a general-purpose “make this process PPL” switch. It requires an ELAM driver and strict certification and code-signing arrangements. A protected antimalware service can also launch a protected child, but this remains a specialized service model. It is a poor fit for an on-demand system utility such as System Informer, which instead used the already-protected services.exe as the root of its launch sequence.
That solution is impressive, but it is also complicated and product-specific. I would not expect every affected software to reproduce it correctly, especially products that currently rely on a simple caller check. For software that cannot run its client as PPL, a broadly reusable answer is still missing. In that sense, I increasingly see BYOTC as a platform problem for Microsoft: Windows needs a supported way for a driver to grant sensitive capabilities to a user-mode principal whose runtime integrity the operating system can preserve.
Microsoft is already working on an adjacent problem. The Windows Endpoint Security Platform is intended to move more antivirus enforcement from third-party kernel drivers into user mode. Reducing kernel code is valuable: a bug in user mode is less likely to crash or corrupt the operating system. But moving an operation out of a vendor driver does not, by itself, answer the BYOTC question. If a privileged service still authenticates a client process that an attacker can modify, the attacker may inject into that trusted client and make the same privileged request from there.
macOS offers a useful contrast. Its security model binds entitlements to a process’s code signature, while the Hardened Runtime enables library validation by default. Apple documents that library validation rejects external code unless it is signed by Apple or by the same team as the main executable. This is not identical to PPL, and it does not make injection impossible, but it makes loading unsigned code into a trusted client substantially harder. Windows does not currently give ordinary third-party applications an equally simple equivalent for this use case.
For antivirus and EDR developers, BYOTC should be treated as an emerging attack surface. Defensively blocking known-abusable drivers can remove obvious building blocks. If your own driver exposes legitimate but dangerous operations, test more than whether the caller’s executable is signed: determine whether an attacker can take control of the trusted client or exploit a trust transition. If you are working on this problem, developing an affected product, or believe you have a more general solution, please feel free to Contact me – I would be happy to chat.
BYOTC also deserves a large-scale study across the Windows driver ecosystem. Related research on vulnerable and abused drivers includes:
- Screwed Drivers—Signed, Sealed, Delivered
- Breaking Boundaries: Investigating Vulnerable Drivers and Mitigating Risks
- Exploring vulnerable Windows drivers
- POPKORN: Popping Windows Kernel Drivers At Scale
- Finding vulnerable drivers with hooking and reading result verification
- Unveiling BYOVD Threats: Malware’s Use and Abuse of Kernel Drivers
- The Windows IOCTL Census: A Corpus-Scale, Multi-Architecture Database of the Driver Control-Code Surface
- IOCTLance: Enhanced Vulnerability Hunting in WDM Drivers with Symbolic Execution and Taint Analysis
- Building a Scalable Windows Driver Vulnerability Analyzer
- 100+ Kernel Bugs in 30 Days
A similar corpus-scale study of trusted user-mode clients would help determine how widespread BYOTC is across the Windows driver ecosystem: how many drivers authorize privileged operations this way, and how many of those trust boundaries can be bypassed.
Conclusion
BYOTC targets the trusted user-mode client of a privileged driver. Malwarebytes showed the direct version—injecting into a signed client—while System Informer showed how a process-creation trust chain could be hijacked. Both cases demonstrate how hard it is to preserve a process’s integrity at runtime.
I have also found a third BYOTC case study, which remains under embargo. If coordination proceeds as expected, I will publish its technical analysis and disclosure story later in September 2026.