Rustware Part 1: Shellcode Process Injection Development
Introduction
Malware development is essential when performing activities like Red Teaming, Adversary Emulation and Network Penetration Testing, the operator can use custom malwares to perform various tasks based on the specific situation. At the same time, analyzing Malwares is useful to learn how malwares work and how to detect them, in order to defend our companies from threat actors. For these reasons we studied several books and courses about Windows Internals, Malware Development and Malware Analysis.
Several threat actors like ALPHV, Hive and Qilin started to develop malware using the Rust programming languages because of its great memory management, speed, and complexity during reverse engineering.
In the last months we started to study and develop custom tools using Rust program language. This first blog post is about the development of a binary that performs an injection of a MessageBox into a target process.
The Shellcode Process Injection we are going to use relies on the use of several WinAPIs: OpenProcess is used to open a handle to the target process, in our case Notepad.exe. We will use this handle to interact with Nodepad.exe, after that, using VirtualAllocEx we can allocate a new region of memory in Notepad.exe with Readable and Writable protection; this region will contain our shellcode written by WriteProcessMemory. Using VirtualProtectEx we can change the memory protection to Readable and Executable to allow CreateRemoteThread to run the shellcode contained in the new allocated memory in Notepad.exe.
The information contained in this blog post is for educational purposes only.
Process Injection
The process injection we are going to develop is a simple Shellcode Injection using the following WinAPIs:
- OpenProcess: gets a handle to the target process
- VirtualAllocEx: allocates memory in the remote target process
- WriteProcessMemory: writes our payload into the allocated memory
- VirtualProtectEx: changes the remote memory protection
- CreateRemoteThread: runs our payload
The payload we are going to use is a message box showing the string “Process Injection”; it was generated using the following msfvenom command.
Rustware Setup
Everything we need to develop a Rust program that leverages on WinAPI, is well described in the Microsoft “Developing with Rust on Windows”. In our case, we used the following software, plugins and crate:
- Visual Studio Code 1.83.0
- Rust-analyzer 0.3.1689
- CodeLLDB 1.10.0
- Crates 0.6.3
- Windows Crate 0.51.1
Rustware Development
First of all, it is necessary to add the Windows Crate to the Cargo.toml file to use the WinAPIs, as shown below.
Each WinAPI requires a feature that must be written in the Cargo.toml file; we can see the features in the Windows Crate documentation.
Below, a list of the WinAPIs we are going to use and the features they require:
- OpenProcess: “Win32_System_Threading” and “Win32_Foundation”
- VirtualAllocEx: “Win32_System_Memory”, and “Win32_Foundation”
- WriteProcessMemory: “Win32_System_Diagnostics_Debug” and “Win32_Foundation”
- VirtualProtectEx:”Win32_System_Memory”, and “Win32_Foundation”
- CreateRemoteThread : “Win32_System_Threading”, “Win32_Foundation” and “Win32_Security”.
After adding all the features, the Cargo.toml file will look like this.
Each feature must also be imported in the code; we can achieve this with the use declaration as shown in the image below.
In order to get the PID of the target process as argument, we can use the std::env::args. The code below checks if the user has specified the PID as argument, if not, it prints the usage string, otherwise it saves the value in the pid variable.
The payload is contained in an array of 272 unsigned 8-bit integers. The payload length is calculated using the len() function, and the payload_ptr variable (use std::ffi::c_void) contains a pointer to the payload, we get it by using the as_ptr() to get a raw pointer to the payload and then casting the raw pointer to a *const c_void.
The Inject function takes three parameters: the PID of the target process, a pointer to the payload, and the payload length.
The WinAPIs we are going to use are defined as unsafe function, because Rust can’t guarantee the memory safety, so it’s our responsibility. In the image below we can see OpenProcess implementation, it is declared as an unsafe function.
In order to use an unsafe function, we need to add an unsafe block; we are going to use this block for all the WinAPIs. Let’s use OpenProcess to get a handle to the target process. It returns a Result enum containing the Handle to the opened process, or the error if it fails. Using the match construct we can use the variants Ok() and Err() to define what to do if the function succeeds or fails.
If the function succeeds the program performs VirtualAllocEx to allocate a region of memory that is readable and writable into a remote target process and print the allocated memory address, otherwise it prints an error message. We don’t specify the lpaddress because we want the WinAPI to determinate where to allocate the region of memory.
After that, we have to write our payload in the new allocated memory. To do that we use WriteProcessMemory.
Using VirtualProtectEx, it is possible to change the memory protection to PAGE_EXECUTE_READ to make the payload executable.
At this point we can execute our payload using. The transmute function allows us to convert the pointer to our payload into a pointer to a function to be executed by the new created Thread as required by the WinAPI.
To compile the program into a 32bit binary we can use the target flag specifying the i686 architecture.
Running the binary we successfully inject our MessageBox into Notepad.exe.
Debugging
Using Process Hacker and x32dbg we can debug our binary to understand how it works under the hood. In x32dbg we can change the command line to specify the target process PID and set the breakpoints on the WinAPIs that our binary is going to use.
OpenProcess
Running the debugger, we can see that OpenProcess is correctly getting the tree parameters:
- 0x1FFFFF is PROCESS_ALL_ACCESS
- 0 is false
- 0x1F80 is the Notepad.exe PID in hex
We can see the Notepad.exe handle in our binary handles list.
VirtualAllocEx
VirtualAllocEx stack contains the following parameters:
- 0x160 is the Notepad.exe handle
- 0x0 is None
- 0x110 is the payload length
- 0x1000 is MEM_COMMIT value
- 0x4 is the PAGE_READWRITE value
We can confirm it by using Process Hacker and inspecting the Notepad.exe memory, as we can see a new allocated memory with RW protection exists at address 0x4D20000.
WriteProcessMemory
Following the WriteProcessMemory arguments:
- 0x160 is the Notepad.exe handle
- 0x4D20000 is the remote memory address
- 0xD8FC78 is the payload address
- 0x110 is the payload len
- 0x0 is None
We can confirm it in ProcessHacker by inspecting the memory at address 0x4D20000.
VirtualProtectEx
VirtualProtectEx is correctly getting the arguments:
- 0x160 is the Notepad.exe handle
- 0x4D20000 is the remote memory address
- 0x110 is the payload length
- 0x20 is the PAGE_EXECUTE_READ value
- 0xD8FD98 is the old_protect variable address
In ProcessHacker we can see the protection flag changed to RX.
CreateRemoteThread
Lastly, the CreateRemoteThread arguments are:
- 0x160 is the Notepad.exe handle
- 0x0 is None
- 0x0 is 0
- 0x4D20000 is the remote memory address to be executed
- 0x0 is None
- 0x0 is 0
- 0x0 is None
By continuing the execution, our MessageBox wil popup.
Conclusion
Rust is a very powerful language; in the last years it found its way into the malware development, especially for ransomware because of its speed. The interaction with WinAPI is not very easy because of the datatype mismatch.
Rust performs security checks at compile and runtime that prevent some of the infamous bugs, since unsafe blocks lack of security checks, we need to be careful when developing malwares because WinAPIs we saw are defined as unsafe function.
In the next blog post we would like to show how to implement other techniques and how to reverse engineering Rust malwares.
Because we are new to Rust, any feedback will be appreciated.
References
- https://learn.microsoft.com/en-us/windows/dev-environment/rust/
- https://microsoft.github.io/windows-docs-rs/doc/windows/
- https://socradar.io/why-ransomware-groups-switch-to-rust-programming-language/
- https://crates.io/crates/windows
- https://doc.rust-lang.org/book/
- https://www.ired.team/offensive-security/code-injection-process-injection/process-injection
- https://institute.sektor7.net
- https://maldevacademy.com
By Raffaele Sabato
Team Leader – Cyber Offensive Solutions and Services
Consulthink S.p.A.
info@consulthink.it