A malicious process injecting code into a signed trusted client, which passes caller verification and reaches a privileged kernel driver

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: Malwarebytesmbamchameleon.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

This case is the simplest form of BYOTC:

  1. Bring the signed driver.
  2. Bring its signed user-mode client.
  3. Gain execution inside the client.
  4. Let the client pass the driver’s signer check.
  5. 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.

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.