Microsoft did not isolate Windows Defender in the sandbox, so I did it
- Transfer
Microsoft put its users at considerable risk when it released Windows Defender outside the sandbox. It surprised me. The sandbox is one of the most effective security enhancement techniques. Why did Microsoft isolate other highly likely attack targets in the sandbox, such as the Microsoft Edge JIT code, but leave Windows Defender unprotected?
As a PoC (proof-of-concept), I isolated Windows Defender, and now I post my code in the public domain as a Flying Sandbox Monster . The foundation of Flying Sandbox Monster is AppJailLauncher-rs , a Rust framework for putting untrusted applications into AppContainers. It also allows you to move I / O applications to a TCP server so that the application in the sandbox runs on a completely different machine. This is an additional level of isolation.
In this article, I will describe the process and results of creating this tool, as well as express my thoughts on Rust on Windows.

Flying Sandbox Monster launched Windows Defender in the sandbox to scan the WannaCry binary
Unhindered access of Windows Defender to system resources and the reception of any malicious file formats make it an ideal target for attackers. The key process of the MsMpEng program works as a service with SYSTEM privileges. The scanning component MpEngine supports parsing an astronomical number of file formats. He also has system emulators for various architectures and interpreters for different languages. All this together, executed at the highest level of privileges in Windows. Oops.
All this made me think. How hard will it be to isolate MpEngine using the same technique I used in the CTF community sandboxing competition two years ago?
The first step to isolating Windows Defender is the ability to launch AppContainers. I would like to use AppJailLauncher again , but there was a problem. The original AppJailLauncher was written as a demo. If I knew then, I would write it in C ++ Core , so as not to suffer from memory management. Over the past two years, I tried to rewrite it in C ++, but unsuccessfully (why are dependencies always such a headache?).
But then I got inspiration . Why not rewrite the AppContainer startup code in Rust?
A few months later, after a quick study of Rust tutorials and writing my first code, I got three main pillars for running AppContainers on Rust: this
Next, you needed to understand how to establish an interface with the scanning component of Windows Defender. An example implementation in C and instructions for starting a scan of MsMpEng were in the Tavis Ormandy loadlibrary repository . Porting structures and function prototypes to Rust is easy to automate, although I initially forgot about array fields and function pointers, which caused a lot of different problems; however, thanks to the testing functionality built into Rust, I quickly resolved all my porting errors - and soon I had a minimal test version that scanned the EICAR test file .

Basic architecture of Flying Sandbox Monster
Our example Flying Sandbox Monsterconsists of sandbox wrapper and Malware Protection Engine (MpEngine). A single executable file has two modes: parent and child processes. The mode is determined by the presence of environment variables that are contained
This is not enough for the full operation of PoC, because the Malware Protection Engine did not want to initialize inside the AppContainer. At first I thought it was an access control problem. But after carefully checking the differences in ProcMon (comparing the differences in performance in AppContainer and not in AppContainer), I realized that the problem could be in determining the version of Windows. Tavis code has always reported as a version of Windows XP. My code reported the real version of the host system: in my case, it is Windows 10. Checking in WinDbg proved that the initialization problem really is in this. You had to lie to MpEngine about the host version of Windows. In C / C ++, I would use function hooking with Detours. Unfortunately, for Rust under Windows there is no equivalent library for hooking functions (several libraries for hooking functions turned out to be much “heavier” than I need).a simple library for hooking functions in Rust (only for 32-bit Windows PE).
Since I have already implemented the key components of AppJailLauncher in Rust, why not finish the job and wrap it all in Rust TCP server? I did this, and now I am glad to present you the “second version” of AppJailLauncher - AppJailLauncher-rs .
AppJailLauncher was a TCP server that listened for a specific port and started the AppContainer process for each received TCP connection. I did not want to reinvent the wheel, but mio , the compact I / O library for Rust, simply did not fit. Firstly, its TcpClient did not provide access to the source
In general, my experience with Rust turned out to be very pleasant, despite some slight roughness. Let me mention some features of this programming language that I particularly noticed during the development of AppJailLauncher.
Cargo . Dependency management in C ++ under Windows is really a tedious and complicated matter, especially with links to third-party libraries. Rust cleverly solves this problem with the help of the cargo package manager. There is a large set of packages that solve many typical problems, such as parsing arguments (clap-rs), Windows FFI (winapi-rs, etc.) and processing wide strings.
Inline testing. Unit tests for C ++ applications require a third-party library and a lot of manual work. That's why they are rarely written for small projects, like the first version of AppJailLauncher. In Rust, unit testing is built into the cargo system, where it exists along with the core functionality.
Macro system . In Rust, the macro system works at the level of the abstract syntax tree, unlike the simple replacement engine in C / C ++. Although you need to learn a bit here, Rust macros are completely devoid of the annoying properties of C / C ++ macros, such as naming and scope collisions.
Debugging . Debugging Rust under Windows works as it should. Rust generates WinDbg-compatible debugging symbols (PDB files) that provide seamless debugging of source code.
External function interface. The Windows APIs are written in C / C ++ and it is understood that this is the way to access it. Other languages, like Rust, must use the external function interface (FFI) to access the Windows API. Rust FFI for Windows (winapi-rs) is mostly ready. There are key APIs there, but some not so often used subsystems like APIs for changing the access control list are missing.
Attributes . Setting attributes is quite cumbersome, since they only apply to the next line.
Borrow checker. The concept of ownership is how Rust achieves memory security. Attempts to understand how Borrow Checker works are accompanied by mysterious, unique errors and require hours of reading documentation and tutorials. In the end, it’s worth it: when I “clicked” and I learned the concept, my programming skills dramatically advanced.
Vectors . In C ++, a container
Option and Result Types. The native Option and Result types were supposed to simplify error checking, but actually made it seem more detailed. You can pretend that there are no errors, and simply cause them
Other types and slices . By belonging to types (owned types) and associated slice (for example
The Rust ecosystem for Windows is still growing. You can also create new Rust libraries that simplify the development of security software for Windows. I made the initial versions of several Rust libraries for sandboxing on Windows, PE parsing, and IAT interception. I hope they will be useful to the emerging Rust community on Windows.
Using Rust and AppJailLauncher, I sandboxed Windows Defender, Microsoft's flagship antivirus product. My achievement is both wonderful and a little shameful. The great thing is that the reliable Windows sandbox engine is available for third-party software. It is a shame that Microsoft itself did not isolate Defender. Microsoft bought what will become Windows Defender, in 2004. In those days, such bugs and architectural miscalculations were unacceptable, but understandable. Over the years, Microsoft has set up an excellent security organization for routine testing and fuzzing. She sandboxed critical parts of Internet Explorer in the sandbox. Somehow, Windows Defender got stuck in 2004. Instead of using Project Zero methods and continuously pointing out the symptoms of this inherent flaw, let's move Windows Defender back to the future.
As a PoC (proof-of-concept), I isolated Windows Defender, and now I post my code in the public domain as a Flying Sandbox Monster . The foundation of Flying Sandbox Monster is AppJailLauncher-rs , a Rust framework for putting untrusted applications into AppContainers. It also allows you to move I / O applications to a TCP server so that the application in the sandbox runs on a completely different machine. This is an additional level of isolation.
In this article, I will describe the process and results of creating this tool, as well as express my thoughts on Rust on Windows.

Flying Sandbox Monster launched Windows Defender in the sandbox to scan the WannaCry binary
Plan
Unhindered access of Windows Defender to system resources and the reception of any malicious file formats make it an ideal target for attackers. The key process of the MsMpEng program works as a service with SYSTEM privileges. The scanning component MpEngine supports parsing an astronomical number of file formats. He also has system emulators for various architectures and interpreters for different languages. All this together, executed at the highest level of privileges in Windows. Oops.
All this made me think. How hard will it be to isolate MpEngine using the same technique I used in the CTF community sandboxing competition two years ago?
The first step to isolating Windows Defender is the ability to launch AppContainers. I would like to use AppJailLauncher again , but there was a problem. The original AppJailLauncher was written as a demo. If I knew then, I would write it in C ++ Core , so as not to suffer from memory management. Over the past two years, I tried to rewrite it in C ++, but unsuccessfully (why are dependencies always such a headache?).
But then I got inspiration . Why not rewrite the AppContainer startup code in Rust?
Sandbox creation
A few months later, after a quick study of Rust tutorials and writing my first code, I got three main pillars for running AppContainers on Rust: this
SimpleDacl, Profileand WinFFI.- SimpleDacl is a generic class that takes care of adding and removing simple discretionary ACE access control entries for Windows. Although it
SimpleDaclworks with files and directories, it has several drawbacks. First, it completely rewrites the existing ACL and converts the inherited ACEs to "normal." In addition, it neglects those ACE elements that cannot be parsed (for example, everything exceptAccessAllowedAceandAccessDeniedAce. Note: we do not support mandatory and verification access control entries). - Profile implements the creation of profiles and processes AppContainer. From the profile, we can get the SID that can be used to create ACEs for resources that the AppContainer should have access to.
- WinFFI gives us functions and structures that are not implemented as well in winapi-rs as in useful utility classes / functions. I put a lot of effort into wrapping every source
HANDLEand pointer in Rust objects to control how long they work.
Next, you needed to understand how to establish an interface with the scanning component of Windows Defender. An example implementation in C and instructions for starting a scan of MsMpEng were in the Tavis Ormandy loadlibrary repository . Porting structures and function prototypes to Rust is easy to automate, although I initially forgot about array fields and function pointers, which caused a lot of different problems; however, thanks to the testing functionality built into Rust, I quickly resolved all my porting errors - and soon I had a minimal test version that scanned the EICAR test file .

Basic architecture of Flying Sandbox Monster
Our example Flying Sandbox Monsterconsists of sandbox wrapper and Malware Protection Engine (MpEngine). A single executable file has two modes: parent and child processes. The mode is determined by the presence of environment variables that are contained
HANDLEsfor the scanned file, and communication between the processes. The parent process sets these two values HANDLEsbefore creating the AppContainer child process. Now the isolated child process loads the library of the anti-virus engine and scans the incoming file for viruses.This is not enough for the full operation of PoC, because the Malware Protection Engine did not want to initialize inside the AppContainer. At first I thought it was an access control problem. But after carefully checking the differences in ProcMon (comparing the differences in performance in AppContainer and not in AppContainer), I realized that the problem could be in determining the version of Windows. Tavis code has always reported as a version of Windows XP. My code reported the real version of the host system: in my case, it is Windows 10. Checking in WinDbg proved that the initialization problem really is in this. You had to lie to MpEngine about the host version of Windows. In C / C ++, I would use function hooking with Detours. Unfortunately, for Rust under Windows there is no equivalent library for hooking functions (several libraries for hooking functions turned out to be much “heavier” than I need).a simple library for hooking functions in Rust (only for 32-bit Windows PE).
AppJailLauncher-rs View
Since I have already implemented the key components of AppJailLauncher in Rust, why not finish the job and wrap it all in Rust TCP server? I did this, and now I am glad to present you the “second version” of AppJailLauncher - AppJailLauncher-rs .
AppJailLauncher was a TCP server that listened for a specific port and started the AppContainer process for each received TCP connection. I did not want to reinvent the wheel, but mio , the compact I / O library for Rust, simply did not fit. Firstly, its TcpClient did not provide access to the source
HANDLEssockets under Windows. Secondly, these sockets were not inherited by the AppContainer child process. Because of this, you have to provide another “pillar” to support appjaillauncher-rs: TcpServer .TcpServerResponsible for the asynchronous TCP server and forwarding compatible client socket STDIN/STDOUT/STDERR. socketSockets created by a call cannot redirect standard I / O streams. For proper standard I / O redirection, you need native sockets (like those created through WSASocket). To enable redirection, it TcpServercreates these "native" sockets and does not explicitly prohibit inheritance for them.My experience with Rust
In general, my experience with Rust turned out to be very pleasant, despite some slight roughness. Let me mention some features of this programming language that I particularly noticed during the development of AppJailLauncher.
Cargo . Dependency management in C ++ under Windows is really a tedious and complicated matter, especially with links to third-party libraries. Rust cleverly solves this problem with the help of the cargo package manager. There is a large set of packages that solve many typical problems, such as parsing arguments (clap-rs), Windows FFI (winapi-rs, etc.) and processing wide strings.
Inline testing. Unit tests for C ++ applications require a third-party library and a lot of manual work. That's why they are rarely written for small projects, like the first version of AppJailLauncher. In Rust, unit testing is built into the cargo system, where it exists along with the core functionality.
Macro system . In Rust, the macro system works at the level of the abstract syntax tree, unlike the simple replacement engine in C / C ++. Although you need to learn a bit here, Rust macros are completely devoid of the annoying properties of C / C ++ macros, such as naming and scope collisions.
Debugging . Debugging Rust under Windows works as it should. Rust generates WinDbg-compatible debugging symbols (PDB files) that provide seamless debugging of source code.
External function interface. The Windows APIs are written in C / C ++ and it is understood that this is the way to access it. Other languages, like Rust, must use the external function interface (FFI) to access the Windows API. Rust FFI for Windows (winapi-rs) is mostly ready. There are key APIs there, but some not so often used subsystems like APIs for changing the access control list are missing.
Attributes . Setting attributes is quite cumbersome, since they only apply to the next line.
Borrow checker. The concept of ownership is how Rust achieves memory security. Attempts to understand how Borrow Checker works are accompanied by mysterious, unique errors and require hours of reading documentation and tutorials. In the end, it’s worth it: when I “clicked” and I learned the concept, my programming skills dramatically advanced.
Vectors . In C ++, a container
std::vectorcan expose its support buffer to another code. The original vector remains valid even if the support buffer is changed. In the case Vecof Rust, this is not so. It Vecrequires the creation of a new object from the "blanks" of the old Vec. Option and Result Types. The native Option and Result types were supposed to simplify error checking, but actually made it seem more detailed. You can pretend that there are no errors, and simply cause them
unwrap, but this will lead to a failure of the runtime when it inevitably gets out Error(or None). Other types and slices . By belonging to types (owned types) and associated slice (for example
String/str, PathBuf/Path) you need a little getting used to. They go in pairs with similar names, but they behave differently. In Rust, an owned type represents an extensible, mutable object (usually a string). A slice is a type of immutable character buffer (usually also a string).Future
The Rust ecosystem for Windows is still growing. You can also create new Rust libraries that simplify the development of security software for Windows. I made the initial versions of several Rust libraries for sandboxing on Windows, PE parsing, and IAT interception. I hope they will be useful to the emerging Rust community on Windows.
Using Rust and AppJailLauncher, I sandboxed Windows Defender, Microsoft's flagship antivirus product. My achievement is both wonderful and a little shameful. The great thing is that the reliable Windows sandbox engine is available for third-party software. It is a shame that Microsoft itself did not isolate Defender. Microsoft bought what will become Windows Defender, in 2004. In those days, such bugs and architectural miscalculations were unacceptable, but understandable. Over the years, Microsoft has set up an excellent security organization for routine testing and fuzzing. She sandboxed critical parts of Internet Explorer in the sandbox. Somehow, Windows Defender got stuck in 2004. Instead of using Project Zero methods and continuously pointing out the symptoms of this inherent flaw, let's move Windows Defender back to the future.