Compare commits

...

4 commits

2 changed files with 65 additions and 33 deletions

View file

@ -8,14 +8,12 @@ use log::{debug, error, info, trace};
use path_clean::PathClean; use path_clean::PathClean;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use windows::core::{PSTR, s}; use windows::core::{PSTR, s};
use windows::Win32::Foundation::{CloseHandle, GetLastError, HANDLE}; use windows::Win32::Foundation::{CloseHandle, GetLastError, HANDLE, NTSTATUS};
use windows::Win32::System::Diagnostics::Debug::WriteProcessMemory; use windows::Win32::System::Diagnostics::Debug::WriteProcessMemory;
use windows::Win32::System::LibraryLoader::{GetModuleHandleA, GetProcAddress}; use windows::Win32::System::LibraryLoader::{GetModuleHandleA, GetProcAddress};
use windows::Win32::System::Memory::{MEM_COMMIT, MEM_RELEASE, MEM_RESERVE, PAGE_READWRITE, use windows::Win32::System::Memory::{MEM_COMMIT, MEM_RELEASE, MEM_RESERVE, PAGE_READWRITE,
VirtualAllocEx, VirtualFreeEx}; VirtualAllocEx, VirtualFreeEx};
use windows::Win32::System::Threading::{CREATE_SUSPENDED, CreateProcessA, CreateRemoteThread, use windows::Win32::System::Threading::{CREATE_SUSPENDED, CreateProcessA, CreateRemoteThread, GetExitCodeProcess, PROCESS_INFORMATION, ResumeThread, STARTUPINFOA, TerminateProcess, TerminateThread, WaitForSingleObject};
PROCESS_INFORMATION, ResumeThread, STARTUPINFOA,
TerminateProcess, TerminateThread, WaitForSingleObject};
type FarProcUnwrapped = unsafe extern "system" fn() -> isize; type FarProcUnwrapped = unsafe extern "system" fn() -> isize;
type LPThreadStartRoutine = unsafe extern "system" fn(param: *mut c_void) -> u32; type LPThreadStartRoutine = unsafe extern "system" fn(param: *mut c_void) -> u32;
@ -84,20 +82,25 @@ fn yes() -> bool {
#[derive(Serialize, Deserialize, Debug, Default)] #[derive(Serialize, Deserialize, Debug, Default)]
pub struct Environment { pub struct Environment {
vars: Option<Vec<String>>, pub vars: Option<Vec<String>>,
#[serde(default = "yes")] #[serde(default = "yes")]
use_system_env: bool, pub use_system_env: bool,
#[serde(default)] #[serde(default)]
environment_append: bool, pub environment_append: bool,
} }
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
pub struct Launcher { pub struct Launcher {
executable_file: String, pub executable_file: String,
cmd_line_args: Option<String>, pub cmd_line_args: Option<String>,
current_dir: Option<String>, pub current_dir: Option<String>,
#[serde(default)] #[serde(default)]
dll_list: Vec<String>, pub dll_list: Vec<String>,
}
pub struct Context {
pub proc_info: PROCESS_INFORMATION,
pub environment: Vec<u8>,
} }
fn absolute_path(cwd: &Path, path: impl AsRef<Path>) -> PathBuf { fn absolute_path(cwd: &Path, path: impl AsRef<Path>) -> PathBuf {
@ -159,7 +162,7 @@ fn inject_standard(h_target: HANDLE, dll_path: &str) -> Result<(), Error> {
Ok(()) Ok(())
} }
pub fn spawn_process<'a>(launcher: Launcher, env: Environment) -> Result<(), Error<'a>> { pub fn spawn_process<'a>(launcher: Launcher, env: Environment) -> Result<Context, Error<'a>> {
let working_dir = match &launcher.current_dir { let working_dir = match &launcher.current_dir {
None => std::env::current_dir()?, None => std::env::current_dir()?,
Some(dir) => PathBuf::from_str(dir.as_str())? Some(dir) => PathBuf::from_str(dir.as_str())?
@ -185,7 +188,8 @@ pub fn spawn_process<'a>(launcher: Launcher, env: Environment) -> Result<(), Err
|cmd_line_args| CString::new(cmd_line_args).unwrap() // TODO: avoid this panic! |cmd_line_args| CString::new(cmd_line_args).unwrap() // TODO: avoid this panic!
); );
trace!("cmd_line_args: {:?}", cmd_line_args); trace!("cmd_line_args: {:?}", cmd_line_args);
let environment = match env.vars { let mut environment: Vec<u8> = Vec::with_capacity(4096);
let environment_ptr = match env.vars {
None => None, None => None,
Some(variables) => { Some(variables) => {
let mut system_variables = match env.use_system_env { let mut system_variables = match env.use_system_env {
@ -209,15 +213,14 @@ pub fn spawn_process<'a>(launcher: Launcher, env: Environment) -> Result<(), Err
CString::new(format!("{key}={value}")).unwrap() // TODO: avoid this panic! CString::new(format!("{key}={value}")).unwrap() // TODO: avoid this panic!
}).collect::<Vec<_>>(); }).collect::<Vec<_>>();
let mut result = Vec::with_capacity(4096);
for var in vars { for var in vars {
result.extend_from_slice(var.as_bytes_with_nul()); environment.extend_from_slice(var.as_bytes_with_nul());
} }
result.extend_from_slice(b"\0"); environment.extend_from_slice(b"\0");
Some(result.as_ptr() as *const c_void) Some(environment.as_ptr() as *const c_void)
} }
}; };
trace!("environment: {:?}", environment); trace!("environment: {:?}", environment_ptr);
let current_directory = CString::new(path_buf_to_str!(working_dir)?)?; let current_directory = CString::new(path_buf_to_str!(working_dir)?)?;
@ -232,7 +235,7 @@ pub fn spawn_process<'a>(launcher: Launcher, env: Environment) -> Result<(), Err
None, None,
false, false,
CREATE_SUSPENDED, CREATE_SUSPENDED,
environment, environment_ptr,
lp_current_directory, lp_current_directory,
&startup_info, &startup_info,
&mut proc_info, &mut proc_info,
@ -253,11 +256,30 @@ pub fn spawn_process<'a>(launcher: Launcher, env: Environment) -> Result<(), Err
debug!("Injection of dll: {inject} succeeded!"); debug!("Injection of dll: {inject} succeeded!");
} }
unsafe { unsafe { ResumeThread(proc_info.hThread); }
ResumeThread(proc_info.hThread);
CloseHandle(proc_info.hThread)?;
CloseHandle(proc_info.hProcess)?;
}
info!("Successful injection finished"); info!("Successful injection finished");
Ok(()) Ok(Context { proc_info, environment })
}
pub fn is_process_running(proc_info: PROCESS_INFORMATION) -> bool {
let mut exit_code = 0u32;
let result = unsafe { GetExitCodeProcess(proc_info.hProcess, &mut exit_code) };
match result {
Ok(_) => {
match NTSTATUS(exit_code as i32) {
windows::Win32::Foundation::STILL_ACTIVE => true,
_ => {
unsafe {
CloseHandle(proc_info.hThread).unwrap();
CloseHandle(proc_info.hProcess).unwrap();
}
false
}
}
}
Err(err) => {
error!("Error while getting exit result: {err:?}");
true
}
}
} }

View file

@ -1,5 +1,5 @@
use clap::Parser; use clap::Parser;
use log::{error, trace}; use log::{error, info, trace};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
@ -62,7 +62,7 @@ impl LogLevel {
} }
#[inline] #[inline]
fn run(config_file: String) -> Result<(), Error> { fn run(config_file: String) -> Result<injector::Context, Error> {
// Load launcher config // Load launcher config
let content = match std::fs::read_to_string(&config_file) { let content = match std::fs::read_to_string(&config_file) {
Ok(content) => Ok(content), Ok(content) => Ok(content),
@ -71,8 +71,10 @@ fn run(config_file: String) -> Result<(), Error> {
let configuration: Configuration = toml::from_str(content.as_str())?; let configuration: Configuration = toml::from_str(content.as_str())?;
trace!("{:?}", configuration); trace!("{:?}", configuration);
injector::spawn_process(configuration.launcher, configuration.environment.unwrap_or_default())?; Ok(injector::spawn_process(
Ok(()) configuration.launcher,
configuration.environment.unwrap_or_default(),
)?)
} }
fn main() { fn main() {
@ -80,8 +82,16 @@ fn main() {
colog::default_builder() colog::default_builder()
.filter_level(args.log_level.unwrap_or_default().into_level_filter()) .filter_level(args.log_level.unwrap_or_default().into_level_filter())
.init(); .init();
if let Err(err) = run(args.config_file) { match run(args.config_file) {
error!("{}", err.to_string()) Ok(context) => {
// Since environment pointer has to outlive the process, here we can do several things,
// either we join the thread of the process, or we just wait
while injector::is_process_running(context.proc_info) {
std::thread::sleep(std::time::Duration::from_secs(1))
}
info!("Application exited");
}
Err(err) => error!("{}", err.to_string())
} }
} }