Shelved Projects #1: Windows Automata
The Vision: Safe and Isolated Desktop Automation
The core goal of Windows Automata was to build a powerful way to automate Windows desktop applications without compromising the safety and integrity of the host system. When you run automated agents-especially those executing dynamic, complex, or third-party workflows-you expose the host system to potential risks. An automation script or automated application could delete critical files, corrupt registry settings, or access private directories.
I wanted a framework that could:
- Traverse and interact with native Windows desktop applications programmatically.
- Isolate the execution completely, ensuring that the target applications under automation could never modify the host file system or write to registry keys.
To achieve this, I combined a C# UI Automation server with a custom-built user-mode sandbox injector (wuias_shield.dll). This sandbox intercepted and redirected all file and registry modifications on the fly, creating a lightweight, isolated workspace for every automated run.
The Engineering Challenge: Hooking Chrome Without Triggering Security Crashes
The hardest part of this project wasn't mapping the UI tree or writing the JSON-RPC pipe handler. It was API Hooking inside modern web browsers. To sandbox an application (like Chrome) in user-mode, you have to intercept system calls like NtCreateFile and NtCreateKey.
Normally, developers use Inline Hooking (also known as a Detour or Trampoline). This involves overwriting the first few bytes of the target function's machine code in memory with a JMP instruction pointing to your hooked function. However, modern browsers implement Arbitrary Code Guard (ACG) and other Code Integrity mitigations. ACG prevents pages from being marked as writable and executable at the same time (PAGE_EXECUTE_READWRITE). If your DLL tries to write to the assembly code section of ntdll.dll or kernel32.dll to install a trampoline, Chrome will immediately crash with a status violation, or the OS container will reboot.
The Solution: Import Address Table (IAT) Hooking
To bypass ACG, we abandoned inline hooking and built an Import Address Table (IAT) Hooking Engine. How does IAT Hooking work conceptually? When a compiled PE (Portable Executable) binary calls a function from an external DLL (like ntdll.dll), it doesn't call the function address directly. Instead:
- The binary points to a special lookup table called the Import Address Table (IAT).
- The IAT is filled with pointers to the actual function addresses, resolved by the OS loader at runtime.
- Crucially, the IAT resides in a writable data section (
.rdata) rather than the read-only, execute-only code section (.text).
Instead of rewriting assembly instructions in the code segment, IAT Hooking simply changes the function pointer inside this table to point to our custom hooked function. Because the table is writable, Chrome's Arbitrary Code Guard (ACG) doesn't flag it, and the application remains perfectly stable.
[Normal Flow]
App Code ---> Call [IAT Pointer] ---> Original API (ntdll.dll)
[Hooked Flow]
App Code ---> Call [IAT Pointer] ---> Hooked API (wuias_shield.dll) ---> Original API (ntdll.dll)
| (Swapped Pointer)
Traversing Modules via the PEB (Process Environment Block)
To apply this hook to every module loaded by Chrome, I had to find them all. I couldn't use standard Windows APIs like CreateToolhelp32Snapshot because they can be blocked in hardened sandboxes, or trigger deadlocks in suspended threads.
Technical Deep Dive: The PEB Structure
The Process Environment Block (PEB) is an internal, undocumented (or semi-documented) Windows data structure containing metadata about the active process. One of its key fields, Ldr, points to a loader data structure (PEB_LDR_DATA) which maintains double-linked lists of all loaded modules (such as InMemoryOrderModuleList).
By reading the PEB pointer directly from CPU registers (specifically the GS register on x64 Windows systems) using assembly intrinsics, my injected DLL could traverse this linked list manually in memory. This allowed the hook injector to locate the base address of every loaded module and patch its IAT table without calling standard Windows APIs that could be intercepted by security policies or trigger loader-lock deadlocks.
Dynamic Hook Propagation
What happens when Chrome dynamically loads a new DLL while running? If my hooking engine only ran once at startup, the newly loaded DLL would bypass the sandbox entirely. To solve this, I hooked LdrLoadDll (the internal NT API that handles library loading). Every time the application loads a new module:
- My hook intercepts the load request.
- I let the original OS loader map the new DLL into memory.
- Before returning control, I trigger my IAT patching engine to scan the newly loaded module and install my hooks.
This dynamic propagation ensures that no matter when a DLL is loaded, it is instantly bound to the sandbox rules.
Sandboxing Files and Registry: Copy-on-Write (CoW)
Once the hooks were securely installed, I implemented Copy-on-Write (CoW) Redirection for both the filesystem and registry.
1. Filesystem Virtualization
Every time the application calls NtCreateFile or NtOpenFile with write access, my hook intercepts the path:
- Write Requests: If Chrome tries to write to a path (e.g.,
C:\ImportantData\file.txt), I catch the write, create a mirrored directory structure inside my safe<sandbox_root>\redirect\<session_id>\files\, and swap the target path pointer to point to the sandbox. - Read Requests: If the application tries to read a file, I check if a modified copy exists in my sandbox. If it does, we serve the sandboxed version. If it doesn't, we let the read pass through to the real filesystem.
2. Registry Redirection
Windows apps write configuration parameters directly to the Registry. To prevent host contamination, I redirected registry writes to an isolated subkey under the user's registry hive:
- Writes to
HKCU\Software\...orHKLM\Software\...were transparently mapped toHKCU\Software\WUIAS_Sandbox\<session_id>\HKCUor\HKLM.
This meant that the automated browser believed it was modifying standard system settings, while the host OS remained completely untouched.
Why Windows Automata was Shelved
If the project worked so well, why did it end up on the shelf? The answer is simple: it was succeeded by my next big project. Windows Automata successfully laid the essential engineering foundation and validated the core user-mode sandboxing paradigms I needed. However, my focus and development resources were soon transitioned to build Talent by UnitBuilds (TUB)-which is the subject of Shelved Projects #2. Windows Automata served its purpose perfectly as a proof of concept, and its architecture directly paved the way for TUB.
Lessons Learned
Building Windows Automata taught me a massive amount about:
- Windows Internals: Walking undocumented OS structures like the PEB and patching portable executables directly in memory.
- Low-Level Synchronization: Dealing with thread-local variables and recursion guards to prevent stack overflows (where a hooked function recursively calls something that triggers the hook again).
- The Trade-offs of User-Mode Virtualization: While user-mode sandboxing is incredibly lightweight, it requires a lot of low-level maintenance compared to kernel-level virtualization or hardware-isolated virtual machines.
Windows Automata was a wild engineering ride. The source code is now archived, but the design patterns-walking the PEB, patching the IAT, and using named pipes for low-overhead telemetry-remain incredibly useful patterns in system programming. Let me know in the comments: Should I revive this project or keep it shelved?
Comments
No comments yet. Start the discussion.