Analyzing Malware: BSOD
Malware Name: BSOD (Blue Screen of Death)
The infamous blue screen of death — I’m sure many of you have come across this at least once in your lifetime, and it probably did so at the exact moment you were working on a paper due at midnight… which you forgot to save.
Let’s dive into the workings of this malware:
- Language Written In: Rust
- Target System: Windows Operating System (Tested on Windows 11 24H2?)
- Payload Delivery Method: Executable dropping (via phishing, USB, downloader trojan), Living-off-the-Land Binaries, Code injection (inject compiled binary into another running process)
- Malware Classification: Disruptive Malware
- Purpose: Triggers Blue Screen of Death on machines running Windows
- Vulnerability Being Exploited: Abuse of system-level privileges
- Defensive Measures: SecureBoot/Defender will block BSOD trigger or behavior may be detected via EDR
How the malware works:
-The script first hides the console; by doing so, the victim user is unaware of anything happening on their machine.
ShowWindow(console_window, SW_HIDE);
-The script then sets itself as a high priority process, which means this process is given more CPU time and scheduling preference, this process becomes the CPU’s main focus of attention.
SetPriorityClass(GetCurrentProcess(), HIGH_PRIORITY_CLASS);
-The next piece of code calls the function RtlAdjustPrivilege, which is a low-level Windows API used to enable or disable privileges for the current process or thread. This is run to enable shutdown privileges
let mut enabled: BOOLEAN = 0;
let privilege = RtlAdjustPrivilege(19, 1 as BOOLEAN, 0 as BOOLEAN, &mut enabled);
Function:
RtlAdjustPrivilege (ULONG Privilege, BOOLEAN Enable, BOOLEAN CurrentThread, PBOOLEAN Enabled)
Privilege Escalation Details
- 19 → Privilege
- The privilege ID, 19 enables the Shutdown privilege.
- 1 → Enable
- Enables the privilege.
- 0 → CurrentThread
- Specifies that the process token should be adjusted (applies shutdown privilege to the whole process instead of a singular thread).
- &mut enabled → Enabled
- Reports back whether the privilege was previously enabled.
After this function runs, a check is performed to determine whether it executed successfully. If successful, the program will continue. If not, it will run a cleanup function and exit (explained later).
Code that checks whether call to RtlAdjustPrivilege was successful:
if privilege != STATUS_SUCCESS {
error_ret = privilege;
cleanup(error_ret);
return;
}
------------------------------------------------------------------------------------------------------------------------------------------------------
-Next, the code for triggering the BSOD is executed.
System Time Breakdown
- SystemTime::now()
- Gets the current system time.
- .duration_since(SystemTime::UNIX_EPOCH)
- Calculates time since Jan 1, 1970 (Unix epoch).
- .unwrap()
- Panics if the time is earlier than the epoch (might occur if the system clock is broken).
- .as_secs() as u32
- Gets the number of whole seconds in the duration (returns a 64-bit number) and casts it to a 32-bit unsigned integer.
- & 0xF_FFFF
- Applies a bitmask to keep only the lowest 20 bits.
*A bitmask is a binary number used to select, test, or clear specific bits in another number using bitwise operations. Can be used to isolate or manipulate specific bits in a value.
Using the resulting value, a random BSOD error code is generated:
let bsod_code = 0xC000_0000 | ((random & 0xF00) << 8) | ((random & 0xF0) << 4) | (random & 0xF);
NTSTATUS Error Code Construction
- 0xC000_0000
- NTSTATUS code structure prefix.
- random & 0xF00
- Gets bits 8–11 from
random, then<< 8moves them to bits 16–19. - random & 0xF0
- Gets bits 4–7 from
random, then<< 4moves them to bits 8–11. - random & 0xF
- Gets bits 0–3 from
randomand keeps them in place.
The result of this bit manipulation creates a random error code, such as 0xC0000026.
------------------------------------------------------------------------------------------------------------------------------------------------------
After the random error code is generated, it is used as a parameter in the function “NtRaiseHardError” which is an undocumented Windows Native API function used in lower-level code. It is what triggers the BSOD
let bsod = NtRaiseHardError(bsod_code as NTSTATUS, 0, 0, null_mut(), 6, &mut u_resp);
Function:
NtRaiseHardError (ErrorStatus, NumberOfParameters, UnicodeStringParameterMask, Parameters, ResponseOption, Response)
Function Parameters
- bsod_code as NTSTATUS
- 32-bit error code generated previously.
- 0 → NumberOfParameters
- No parameters passed to the error message.
- 0 → UnicodeStringParameterMask
- Optional parameter used to specify which parameters (in the upcoming parameters array) are strings — none in this case.
- null_mut() → Parameters
- Array of pointers to parameters that will be used to format the error message string — none in this case.
- ResponseOption → 6
- Defines whether the user can choose to ignore the error, continue, or take other actions. Value 6 represents OptionShutDownSystem.
- Response → &mut u_resp
- A pointer to the variable that will store the user’s response to the error dialog (but it is ignored here).
------------------------------------------------------------------------------------------------------------------------------------------------------
The next piece of code checks to see whether the call to NtRaiseHardError failed, and if so, performs a cleanup and returns early
if bsod != STATUS_SUCCESS {
error_ret = bsod;
cleanup(error_ret);
return;
}
Checks to see if bsod succeeded (Usually represented by the value 0x00000000)
If the call to NtRaiseHardError fails, this indicates that the function was run with insufficient privileges, was blocked or there were invalid parameters.
If the function call does fail, the value is stored in the variable error_ret which is then passed into the cleanup function.