Compare commits
5 commits
Author | SHA1 | Date | |
---|---|---|---|
4a7738dd3d | |||
69250cf856 | |||
147709d09b | |||
50aee7dd64 | |||
d6d0e63dd0 |
4 changed files with 77 additions and 34 deletions
|
@ -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 {
|
||||||
|
@ -200,6 +204,7 @@ pub fn spawn_process<'a>(launcher: Launcher, env: Environment) -> Result<(), Err
|
||||||
if entry.is_some() & env.environment_append {
|
if entry.is_some() & env.environment_append {
|
||||||
// Usually when injecting you want your modifications to take precedence so
|
// Usually when injecting you want your modifications to take precedence so
|
||||||
// prepend is the correct operation
|
// prepend is the correct operation
|
||||||
|
// Evaluate: std::env::join_paths
|
||||||
value = format!("{value}{ENV_VAR_SEPARATOR}{}", entry.unwrap()); // Has to hold value
|
value = format!("{value}{ENV_VAR_SEPARATOR}{}", entry.unwrap()); // Has to hold value
|
||||||
}
|
}
|
||||||
system_variables.insert(key, value); // If key exists, it will be replaced
|
system_variables.insert(key, value); // If key exists, it will be replaced
|
||||||
|
@ -209,15 +214,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 +236,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 +257,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
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -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())
|
||||||
}
|
}
|
||||||
}
|
}
|
10
samples/nte.toml
Normal file
10
samples/nte.toml
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
[launcher]
|
||||||
|
executable_file = 'HTGame.exe'
|
||||||
|
cmd_line_args = ''
|
||||||
|
current_dir = '<REPLACE_THIS_FOR_YOUR_GAME_FOLDER>\Client\WindowsNoEditor\HT\Binaries\Win64'
|
||||||
|
dll_list = ['<FULL_PATH_TO_NTR_DLL>']
|
||||||
|
|
||||||
|
[environment]
|
||||||
|
#vars = ['TESTVAR1=AAAAAA', 'TESTVAR2=AAAAAA']
|
||||||
|
#use_system_env = true
|
||||||
|
#environment_append = false
|
|
@ -5,6 +5,6 @@ current_dir = '<REPLACE_THIS_FOR_YOUR_GAME_FOLDER>\Client\Binaries\Win64'
|
||||||
dll_list = ['<FULL_PATH_TO_SHOREKEEPER_DLL>']
|
dll_list = ['<FULL_PATH_TO_SHOREKEEPER_DLL>']
|
||||||
|
|
||||||
[environment]
|
[environment]
|
||||||
#environment = ['TESTVAR1=AAAAAA', 'TESTVAR2=AAAAAA']
|
#vars = ['TESTVAR1=AAAAAA', 'TESTVAR2=AAAAAA']
|
||||||
#use_system_env = true
|
#use_system_env = true
|
||||||
#environment_append = false
|
#environment_append = false
|
Loading…
Reference in a new issue