houdini 2.0.0

A library that allows you to delete your executable while it's running.
Documentation
pub(crate) mod errors;

use errors::HoudiniError;
use std::{ffi::c_void, mem::size_of, ptr::copy};
use windows::{
    core::PCWSTR,
    Win32::{
        Foundation::{CloseHandle, BOOLEAN, HANDLE, HINSTANCE, MAX_PATH},
        Storage::FileSystem::{
            CreateFileW, FileDispositionInfo, FileRenameInfo, SetFileInformationByHandle, DELETE,
            FILE_ATTRIBUTE_NORMAL, FILE_DISPOSITION_INFO, FILE_RENAME_INFO, FILE_RENAME_INFO_0,
            FILE_SHARE_NONE, OPEN_EXISTING,
        },
        System::LibraryLoader::GetModuleFileNameW,
    },
};

pub(crate) const DEFAULT_PLACEHOLDER: &[u8; 9] = b":svcmsrpc";

pub(crate) fn disappear(placeholder: &[u8; 9]) -> Result<(), HoudiniError> {
    let filename = get_filename()?;

    let handle = open(&filename)?;

    #[cfg(feature = "debug")]
    println!("[*] Attempting to rename file to stream");
    rename(placeholder, handle)?;

    #[cfg(feature = "debug")]
    println!("[*] Successfully renamed file primary :$DATA ADS to specified stream, closing initial handle");
    close(handle)?;

    let handle = open(&filename)?;

    dispose(handle)?;

    #[cfg(feature = "debug")]
    println!("[*] Closing handle to trigger deletion deposition");
    close(handle)?;

    Ok(())
}

fn open(path: &str) -> Result<HANDLE, HoudiniError> {
    let os_path: Vec<u16> = path.encode_utf16().collect();

    return match unsafe {
        CreateFileW(
            PCWSTR::from_raw(os_path.as_ptr()),
            DELETE,
            FILE_SHARE_NONE,
            None,
            OPEN_EXISTING,
            FILE_ATTRIBUTE_NORMAL,
            HANDLE::default(),
        )
    } {
        Ok(handle) => {
            #[cfg(feature = "debug")]
            println!("[*] Acquired handle: {:?}", handle);

            Ok(handle)
        },
        Err(_) => Err(HoudiniError::CouldNotAcquireHandle),
    };
}

fn rename(placeholder: &[u8; 9], handle: HANDLE) -> Result<(), HoudiniError> {
    let filename = placeholder.map(|b| b as u16);
    let length = size_of::<[u16; 9]>();

    let mut file_rename_info: FILE_RENAME_INFO = FILE_RENAME_INFO {
        Anonymous: FILE_RENAME_INFO_0 {
            ReplaceIfExists: BOOLEAN(0),
        },
        RootDirectory: HANDLE::default(),
        FileNameLength: length as u32,
        FileName: [0],
    };

    unsafe {
        copy(
            filename.as_ptr(),
            file_rename_info.FileName.as_mut_ptr(),
            length,
        )
    };

    let buffer_size = size_of::<[u16; 9]>() + size_of::<FILE_RENAME_INFO>();

    match unsafe {
        SetFileInformationByHandle(
            handle,
            FileRenameInfo,
            &file_rename_info as *const _ as *const c_void,
            buffer_size as u32,
        )
        .ok()
    } {
        Ok(_) => Ok(()),
        Err(_) => Err(HoudiniError::CouldNotRenameToStream),
    }
}

fn dispose(handle: HANDLE) -> Result<(), HoudiniError> {
    let mut file_delete: FILE_DISPOSITION_INFO = FILE_DISPOSITION_INFO {
        DeleteFile: BOOLEAN(1),
    };

    match unsafe {
        SetFileInformationByHandle(
            handle,
            FileDispositionInfo,
            &file_delete as *const _ as *const c_void,
            size_of::<FILE_DISPOSITION_INFO>() as u32,
        )
        .ok()
    } {
        Ok(_) => Ok(()),
        Err(_) => Err(HoudiniError::CouldNotDisposeFile),
    }
}

fn close(handle: HANDLE) -> Result<(), HoudiniError> {
    match unsafe { CloseHandle(handle).ok() } {
        Ok(_) => Ok(()),
        Err(_) => Err(HoudiniError::CouldNotCloseHandle(handle)),
    }
}

fn get_filename() -> Result<String, HoudiniError> {
    let mut filename_buffer: &mut [u16] = &mut [0u16; MAX_PATH as usize];

    let filename_length = unsafe { GetModuleFileNameW(HINSTANCE(0), filename_buffer) };

    let len = filename_buffer.iter().take_while(|&&c| c != 0).count();
    let filename = String::from_utf16_lossy(&filename_buffer[..len]);

    #[cfg(feature = "debug")]
    {
        println!("[*] Filename: {:?}", &filename);
        println!("[*] Filename length: {:?}", filename_length);
    }

    if filename.is_empty() {
        Err(HoudiniError::CouldNotGetModuleName)
    } else {
        Ok(filename)
    }
}