This website uses cookies

To provide the highest level of service we use cookies on this site.
Your continued use of the site means that you agree to their use in accordance with our terms and conditions.

Insights

Wipe and Rise: How deleting files on Windows enables LPE

Mateusz Lewczak

July 11, 2025

Introduction Time-of-check-to-time-of-use (TOCTOU) race conditions have plagued Windows software for decades, yet they still surface in modern code. During a recent audit of TestedAPP we uncovered a textbook example: the application’s background service first checks whether a cache directory exists and, milliseconds later, deletes it without re-validating the path. Because every non-privileged user can create files and folders inside the application tree, an attacker can win the race, swap the legitimate directory for an NTFS mount point, and redirect the deletion to any location on the system drive. The easiest and most damaging target is the hidden C:\Config.Msi staging folder that Windows Installer treats as fully trusted during install-rollback operations [1, 2].

Exploiting this primitive is straightforward. By combining SetOpLock (to freeze the service just after it issues the delete request) with CreateMountPoint and CreateSymlink from Google Project Zero’s symbolic-link-toolkit, a low-privilege user turns the innocent folder purge into an arbitrary-folder-delete primitive.

Once C:\Config.Msi is wiped, the attacker recreates it as a junction pointing elsewhere and drops a rogue rollback script plus a DLL payload. When Windows Installer resumes and runs the rollback, that DLL is invoked as NT AUTHORITY\SYSTEM, launching a SYSTEM-level command prompt and handing the attacker full control of the machine.

Beyond the immediate privilege escalation impact, the finding underscores two issues: Windows still grants standard users broad write access to many application directories, and developers continue to rely on non atomic file system operations that can be subverted with object manager tricks. The research therefore serves as both an exploitation case study and a practical checklist for hardening installer style code paths against modern race condition attacks. PROOF OF CONCEPTModule Behavior and Attack Vector Tested application module includes a command line utility, downloader.exe, that any local (non-privileged) user can invoke to prefetch or “cache” packages simply by supplying a URL. Because the tool does not validate or restrict the URL parameter, an attacker can run, for example: Upon execution of the cache command, the package download process is initiated by sending a request for the .MANI manifest, which serves as the package descriptor. Two HTTP GET requests are issued:

1. The first request is sent to the manifest generation endpoint to produce the .MANI descriptor for the specified package: 2. The second request is then sent to retrieve that .MANI file’s contents, downloading the manifest into the local cache folder for parsing and subsequent file downloads: A sample .MANI manifest is provided below, listing file names, sizes and checksums: The manifest is saved in the central cache directory: A subdirectory named after the CONTENTID (for example, ABC123) is then created beneath that path: Within this staging folder, each file declared in the manifest is downloaded and its integrity is verified against the provided checksum before final placement. Subsequently, the files listed in the .MANI manifest are fetched via an HTTP GET request. For the Windows entry shown above, the request takes the following form: When the server fails to return the requested file or the downloaded file’s checksum does not match the declared value, all files associated with that package are purged. Critical to this vulnerability, is that the TestedApp.exe service attempts to access a non-existent CachePKG\[CONTENTID]_0_1 directory. As can be seen in the following screenshot from the Procmon64.exe tool (from the SysInternals Suite): Otherwise, if that directory is present, it is deleted by the service: Furthermore, at the very start of the file deletion sequence, a nonexistent file is referenced by the service behavior that can be leveraged during exploitation: A less significant, but noteworthy, behavior is that the application repeatedly loops through the entire HTTP request sequence, generating the .MANI manifest, fetching the .MANI file, attempting to download each file declared within it and purging package data. Exploitation – prerequisites Windows restricts NTFS junction creation to privileged accounts, making traditional symbolic-link attacks infeasible for standard users. However, Google Project Zero research demonstrated that a mount point can be created against the \RPC Control object directory an object manager namespace to which unprivileged processes often have write access effectively emulating a symbolic link without requiring SeCreateSymbolicLinkPrivilege.

To implement this technique, the symboliclink-testing-tools suite was utilized, that can be found under this URL address on GitHub [4]. From this repository, the following utilities were employed:

• SetOpLock.exe was used to establish an opportunistic lock on a file (or directory), preventing its deletion until the lock is released.

• CreateMountPoint.exe was used to bind an otherwise empty filesystem directory to the \RPC Control namespace (or any other writable object directory), thereby redirecting subsequent filesystem operations through the object manager path.

• CreateSymlink.exe was used to generate a file level symbolic link within the object manager namespace, allowing redirection of open, read, write, or delete operations to arbitrary targets without administrative privileges.

By combining these tools pausing the deletion routine with an oplock, mounting an empty folder onto \RPC Control, and then creating an object-manager symlink, an unprivileged user gains an arbitrary-file-delete primitive capable of targeting protected system paths.

Additionally, an .MANI manifest must be provided during the process, upon which the TestedApp service will attempt to cache the package and thereby trigger the application behavior that permits the folder’s deletion. The following Python script, built on the Flask framework, was employed to serve the contents of the requested .MANI manifest: The directory containing the Flask application must also include an mani_file with the following contents: Execution of the script can occur locally or on a remote, attacker controlled machine.

Exploitation steps – folder deletion To successfully exploit the vulnerability, the following steps must be carried out:

1. The .MANI serving endpoint must first be launched by executing: This command starts the Flask-based server, which will listen for incoming requests and return the .MANI manifest when queried.

2. The file EXPLOIT_1.SPARSEMAP is created, then an opportunistic lock is set to pause the file-deletion routine. 3. The package-download process is initiated using Downloader.exe. 4. A mount point is established at the path of the non-existent CachePKG folder. 5. A symbolic link is created from CachePKG\EXPLOIT_0_1 to the directory targeted for deletion. 6. The opportunistic lock is released by pressing ENTER.

Upon completion of these steps, the TestedApp.exe service deletes the specified folder, as illustrated in the following Procmon64.exe screenshot: Exploitation steps – privilege escalation Windows Installer (MSI) performs all file staging and rollback-script generation inside a hidden C:\Config.Msi folder on the system drive, treating everything there as fully trusted installer data before any changes are committed. During a normal installation, new or replaced files are first copied into Config.Msi, and corresponding rollback scripts (.rbs/.rbf) are built there. Only once all staging steps complete successfully are the files moved into their final locations; if staging fails or an explicit rollback/uninstall is invoked, the rollback scripts stored in Config.Msi are executed under the SYSTEM account to undo or finalize all changes.

By exploiting an arbitrary-folder-delete primitive immediately after Config.Msi is created, an attacker can remove the folder and replace it with an NTFS junction. When Windows Installer subsequently issues its cleanup or rollback delete operation against C:\Config.Msi, the junction causes deletion of the protected target directory instead of the original staging area. After deletion of system files, a malicious Config.Msi directory (or junction) can be recreated containing attacker controlled .rbs/.rbf rollback scripts. Upon the next rollback invocation, these scripts run with SYSTEM privileges, yielding arbitrary code execution and full local privilege escalation. A full proof of concept exploit has been published by the Zero Day Initiative on GitHub [3].

It implements a two-phase attack that leverages Windows Installer’s rollback mechanism together with an arbitrary folder delete primitive. During the second phase, an MSI install is launched and then aborted mid-rollback. After C:\Config.Msi has been deleted and recreated with a permissive DACL (Discretionary Access Control List), the installer’s generated rollback script (.rbs) and rollback file (.rbf) are replaced: the .rbf payload is a DLL crafted to launch a command line, and the malicious .rbs directs Windows Installer to copy that DLL file into: When the rollback resumes, these files are executed under the SYSTEM account, causing the DLL to be dropped into the ink directory. Thereafter, each invocation of the On-Screen Keyboard (osk.exe) on the secure desktop (for example, via Ctrl + Alt + Delete) results in the loading of the hijacked HID.dll, which spawns a SYSTEM privileged command prompt via DLL hijacking.

To successfully exploit the vulnerability, the following steps must be carried out:

1. The .MANI–serving endpoint must first be launched by executing: This command starts the Flask-based server, which will listen for incoming requests and return the .MANI manifest when queried.

2. The FolderOrFileDeleteToSystem.exe binary previously compiled from the GitHub repository must be executed as shown: 3. The file EXPLOIT_1.SPARSEMAP is created, then an opportunistic lock is set to pause the file-deletion routine. 4. The package download process is initiated using downloader.exe. 5. A mount point is established at the path of the non-existent CachePKG folder. 6. A symbolic link is created from CachePKG\EXPLOIT_0_1 to the directory targeted for deletion. 7. The opportunistic lock is released by pressing ENTER.

After completion of the above steps, the malicious HID.dll is dropped into: At that point, commands may be executed as NT AUTHORITY\SYSTEM by triggering the On-Screen Keyboard on the secure desktop:

1. Switch to the secure desktop (for example, via Ctrl + Alt + Delete).

2. Run On-Screen Keyboard: 3. The hijacked HID.dll is loaded by osk.exe, spawning a SYSTEM-privileged command shell.
Summary A classic time-of-check-to-time-of-use race condition in TestedAPP’s file-cleanup routine lets an unprivileged user replace the soon-to-be-deleted cache folder with an NTFS mount point, turning the benign deletion into an arbitrary-folder-remove that targets the trusted C:\Config.Msi directory. Once that folder is wiped, the attacker recreates it as a junction containing a rogue rollback script and a tainted DLL; when Windows Installer later runs this content under NT AUTHORITY\SYSTEM, the payload launches with full machine privileges. The episode shows that non-atomic file operations in user-writable paths still pose a serious risk and underscores the importance of keeping sensitive installer data in directories ordinary users cannot modify, re-validating paths right before use, and eliminating unsupervised deletion logic that mount points or junctions can hijack.
References [1] https://www.thezdi.com/blog/2022/3/16/abusing-arbitrary-file-deletes-to-escalate-privilege-and-other-great-tricks-archive [2] https://cloud.google.com/blog/topics/threat-intelligence/arbitrary-file-deletion-vulnerabilities/ [3] https://github.com/thezdi/PoC/tree/main/FilesystemEoPs/FolderOrFileDeleteToSystem [4] https://github.com/googleprojectzero/symboliclink-testing-tools


Next Pentest Chronicles

When Usernames Become Passwords: A Real-World Case Study of Weak Password Practices

Michał WNękowicz

9 June 2023

In today's world, ensuring the security of our accounts is more crucial than ever. Just as keys protect the doors to our homes, passwords serve as the first line of defense for our data and assets. It's easy to assume that technical individuals, such as developers and IT professionals, always use strong, unique passwords to keep ...

SOCMINT – or rather OSINT of social media

Tomasz Turba

October 15 2022

SOCMINT is the process of gathering and analyzing the information collected from various social networks, channels and communication groups in order to track down an object, gather as much partial data as possible, and potentially to understand its operation. All this in order to analyze the collected information and to achieve that goal by making …

PyScript – or rather Python in your browser + what can be done with it?

michał bentkowski

10 september 2022

PyScript – or rather Python in your browser + what can be done with it? A few days ago, the Anaconda project announced the PyScript framework, which allows Python code to be executed directly in the browser. Additionally, it also covers its integration with HTML and JS code. An execution of the Python code in …

Any questions?

Happy to get a call or email
and help!