#![cfg(windows)]
mod macros;
use crate::{cmp, str_from_wide_str, Data, Inject, Injector, Result};
use macros::check_ptr;
use alloc::string::{String, ToString};
#[cfg(all(not(feature = "std"), feature = "alloc"))]
use alloc::vec::Vec;
use core::mem::size_of;
use winapi::shared::minwindef::{DWORD, FALSE, LPVOID, MAX_PATH};
use winapi::um::handleapi::{CloseHandle, INVALID_HANDLE_VALUE};
use winapi::um::processthreadsapi::CreateRemoteThread;
use winapi::um::sysinfoapi::GetSystemWindowsDirectoryW;
use winapi::um::tlhelp32::{
CreateToolhelp32Snapshot, Module32FirstW, Module32NextW, Process32FirstW, Process32NextW,
LPPROCESSENTRY32W, MAX_MODULE_NAME32, MODULEENTRY32W, PROCESSENTRY32W, TH32CS_SNAPMODULE,
TH32CS_SNAPMODULE32, TH32CS_SNAPPROCESS,
};
use winapi::um::winnt::{
PROCESS_CREATE_THREAD, PROCESS_QUERY_INFORMATION, PROCESS_VM_OPERATION, PROCESS_VM_READ,
PROCESS_VM_WRITE,
};
mod mem;
#[cfg(feature = "ntdll")]
mod ntdll;
pub(super) mod process;
mod thread;
const KERNEL32: &'static str = "KERNEL32.DLL";
const SYSWOW64: &'static str = "SysWOW64";
const SYSTEM32: &'static str = "System32";
#[cfg(feature = "ntdll")]
use ntapi::ntwow64::LDR_DATA_TABLE_ENTRY32;
#[cfg(not(feature = "std"))]
use winapi::um::errhandlingapi::GetLastError;
#[cfg(not(feature = "std"))]
use winapi::um::winbase::GetFileInformationByHandleEx;
use crate::error::Error;
use process::Process;
pub fn wide_str_from_str(v: &str) -> Vec<u16> {
let tmp: Vec<u16> = widestring::encode_utf16(v.chars()).collect();
tmp
}
fn canonicalize(p: &crate::Data) -> Result<(String, Option<String>)> {
match p {
#[cfg(not(feature = "std"))]
crate::Data::Str(s) => {
use winapi::um::fileapi::{CreateFileW, GetFinalPathNameByHandleW};
let mut file = wide_str_from_str(s);
file.push(0);
let r = unsafe {
CreateFileW(
file.as_ptr(),
0,
winapi::um::winnt::FILE_SHARE_READ
| winapi::um::winnt::FILE_SHARE_WRITE
| winapi::um::winnt::FILE_SHARE_DELETE,
core::ptr::null_mut(),
winapi::um::fileapi::OPEN_EXISTING,
winapi::um::winbase::FILE_FLAG_BACKUP_SEMANTICS,
core::ptr::null_mut(),
)
};
if r == INVALID_HANDLE_VALUE {
return Err(crate::error::Error::from("CreateFileW in canonicalize"));
}
let mut buf = Vec::<u16>::with_capacity(0);
let size = unsafe {
GetFinalPathNameByHandleW(
r,
buf.as_mut_ptr(),
buf.capacity() as u32,
winapi::um::winbase::VOLUME_NAME_DOS,
)
};
log::trace!("Size of fp is {}", size);
buf.reserve_exact(size as usize + 1);
unsafe {
core::ptr::write_bytes(buf.as_mut_ptr(), 0, size as usize + 1);
}
let size = unsafe {
GetFinalPathNameByHandleW(
r,
buf.as_mut_ptr(),
buf.capacity() as u32,
winapi::um::winbase::VOLUME_NAME_DOS,
)
};
let err = if size == 0 {
0
} else {
unsafe { GetLastError() }
};
const SIZE: usize = 2 + 1 + MAX_PATH;
let mut name = Vec::<u16>::with_capacity(SIZE);
let lps = if unsafe {
core::ptr::write_bytes(name.as_mut_ptr(), 0, SIZE);
name.set_len(SIZE);
GetFileInformationByHandleEx(
r,
winapi::um::minwinbase::FileNameInfo,
name.as_mut_ptr() as *mut winapi::ctypes::c_void,
SIZE as u32,
)
} != 0
{
let size = unsafe { core::ptr::read(name.as_ptr() as *const u32) };
log::info!("Size is {}", size);
let name = &name.as_slice()[2..size as usize + 2];
let name = crate::trim_wide_str::<true>(name);
str_from_wide_str(name).ok().map(|s| {
let index = s.rfind('\\');
if let Some(index) = index {
s.split_at(index).1.trim_start_matches('\\').to_string()
} else {
s
}
})
} else {
let error =
crate::error::Error::from("GetFileInformationByHandleEx in canonicalize");
log::error!("{}", error);
None
};
unsafe {
CloseHandle(r);
}
if size == 0 {
return Err(crate::error::Error::Winapi(
"GetFinalPathNameByHandleW in canonicalize",
err,
));
}
log::info!("Size of fp is {},{}", size, err);
assert!(buf.capacity() >= size as usize);
unsafe { buf.set_len(size as usize) };
let fp = str_from_wide_str(buf.as_slice())?;
return Ok((fp, lps));
}
#[cfg(feature = "std")]
crate::Data::Str(s) => return canonicalize(&crate::Data::Path(std::path::Path::new(*s))),
#[cfg(feature = "std")]
crate::Data::Path(p) => {
use std::os::windows::ffi::OsStrExt;
let s = std::fs::canonicalize(p)?;
let tmp: Vec<u16> = s.as_os_str().encode_wide().collect();
let fp = str_from_wide_str(tmp.as_slice())?;
let lps = s
.file_name()
.map(|v| v.to_str().map(|x| x.to_string()))
.flatten();
return Ok((fp, lps));
}
}
}
#[cfg(not(feature = "std"))]
fn read(file: &Data) -> Result<Vec<u8>> {
use winapi::um::fileapi::{CreateFileW, GetFileSizeEx, ReadFile};
let file = match file {
crate::Data::Str(s) => *s,
};
let mut filew = wide_str_from_str(file);
filew.push(0);
let filew = filew;
let r = unsafe {
CreateFileW(
filew.as_ptr(),
winapi::um::winnt::GENERIC_READ,
winapi::um::winnt::FILE_SHARE_READ
| winapi::um::winnt::FILE_SHARE_WRITE
| winapi::um::winnt::FILE_SHARE_DELETE,
core::ptr::null_mut(),
winapi::um::fileapi::OPEN_EXISTING,
winapi::um::winnt::FILE_ATTRIBUTE_NORMAL,
core::ptr::null_mut(),
)
};
if r == INVALID_HANDLE_VALUE {
return Err(crate::error::Error::from("CreateFileW in read"));
}
let cleanup = || {
return if unsafe { CloseHandle(r) } == 0 {
Err(crate::error::Error::from("CloseHandle in read."))
} else {
Ok(())
};
};
let mut size = winapi::um::winnt::LARGE_INTEGER::default();
if unsafe { GetFileSizeEx(r, &mut size as *mut winapi::um::winnt::LARGE_INTEGER) } == 0 {
let _ = cleanup();
return Err(crate::error::Error::from("GetFileSizeEx in read"));
}
let size = *unsafe { size.QuadPart() };
let mut read = 0;
log::info!("Requested read of {} byte file", size);
let mut file_contents = Vec::<u8>::with_capacity(0);
file_contents.reserve(size as usize);
while read < size {
let mut bytes_read: u32 = 0;
let return_code = unsafe {
ReadFile(
r,
file_contents.as_mut_ptr().add(file_contents.len()) as *mut winapi::ctypes::c_void,
size as u32,
&mut bytes_read as *mut u32,
core::ptr::null_mut(),
)
};
if return_code == 0 {
let _ = cleanup();
return Err(crate::error::Error::from("ReadFile in read"));
}
read += bytes_read as i64;
log::info!("read {} bytes, and {} in total", bytes_read, read);
}
unsafe {
file_contents.set_len(read as usize);
}
cleanup()?;
Ok(file_contents)
}
#[cfg(feature = "std")]
fn read(file: &Data<'_>) -> Result<Vec<u8>> {
let file = match file {
crate::Data::Str(v) => std::path::Path::new(v),
crate::Data::Path(p) => p,
};
std::fs::read(file).map_err(|x| x.into())
}
pub struct InjectWin<'a> {
pub inj: &'a Injector<'a>,
pub wait: bool,
}
impl<'a> Inject for InjectWin<'a> {
fn inject(&self) -> Result<()> {
let proc = Process::new(
self.inj.pid,
PROCESS_CREATE_THREAD
| PROCESS_VM_WRITE
| PROCESS_VM_READ
| PROCESS_VM_OPERATION
| PROCESS_QUERY_INFORMATION,
)?;
let (path, dll_name) = canonicalize(&self.inj.dll)?;
{
match dll_name {
Some(name) => {
if get_module(crate::Data::Str(name.as_str()), &proc).is_ok() {
return Err(Error::Unsupported(Some("dll already injected")));
}
}
None => return Err(crate::error::CustomError::DllPathNoFile.into()),
}
}
let mem = {
let mut full_path = wide_str_from_str(path.as_str());
full_path.push(0);
let mut mempage =
mem::MemPage::new(&proc, full_path.len() * core::mem::size_of::<u16>(), false)?;
mempage.write(full_path.as_slice())?;
mempage
};
self.exec_fn_in_proc(&proc, "LoadLibraryW", mem.get_address())
}
fn eject(&self) -> Result<()> {
const X86EJECTX64: crate::error::Error = crate::error::Error::Unsupported(Some(
"ejecting is not currently supported from a x86 binary targeting a x64 process.",
));
let proc = Process::new(
self.inj.pid,
PROCESS_CREATE_THREAD
| PROCESS_VM_WRITE
| PROCESS_VM_READ
| PROCESS_VM_OPERATION
| PROCESS_QUERY_INFORMATION,
)?;
let name = canonicalize(&self.inj.dll)?.1;
let name = match name {
Some(name) => name,
None => return Err(crate::error::CustomError::DllPathNoFile.into()),
};
let handle = match get_module(crate::Data::Str(name.as_str()), &proc) {
Ok((_, (_, Some(h)))) => h,
Ok(_) => {
return Err(X86EJECTX64);
}
Err(_) => {
return Err(crate::error::Error::InjectLib(
crate::error::CustomError::LibraryNotFound(self.inj.dll.to_string()),
));
}
};
crate::info!("Found dll in proc, with handle:{:#x?}", handle);
self.exec_fn_in_proc(&proc, "FreeLibrary", handle as LPVOID)
}
fn find_pid(name: crate::Data) -> Result<Vec<u32>> {
Self::find_pid_selector(|p| {
return match str_from_wide_str(crate::trim_wide_str::<true>(&p.szExeFile)) {
Ok(str) => match name {
crate::Data::Str(s) => {
crate::debug!("Checking {} against {}", str, s);
return s.ends_with(str.as_str());
}
#[cfg(feature = "std")]
crate::Data::Path(p) => {
crate::debug!("Checking {} against {}", str, p.to_string_lossy());
return p.ends_with(str.as_str());
}
},
Err(e) => {
crate::warn!("Skipping check of process. Can't construct string, to compare against. Err:{:#?}",e);
false
}
};
})
}
}
impl<'a> InjectWin<'a> {
fn exec_fn_in_proc(&self, proc: &Process, entry_fn: &str, param: LPVOID) -> Result<()> {
if self.inj.pid == 0 {
crate::warn!("Supplied id is 0. Will not inject, as it is not supported by windows.");
return Err(Error::Unsupported(Some(
"PID 0 is an invalid target under windows.",
)));
}
let self_proc = Process::self_proc();
crate::debug!("Process is {}", proc);
let pid_is_under_wow = proc.is_under_wow()?;
let self_is_under_wow = self_proc.is_under_wow()?;
crate::info!(
"pid_is_under_wow:{},self_is_under_wow:{}",
pid_is_under_wow,
self_is_under_wow
);
if self_is_under_wow && !pid_is_under_wow {
if cfg!(feature = "ntdll") {
crate::warn!("This injection will use a slightly different method, than usually. This is normal, when the injector is x86, but the pid specified is a x64 process.\
We will be using ntdll methods. The ntdll.dll is technically not a public facing windows api.");
} else {
return Err(Error::Unsupported(Some("Cannot continue injection. You are trying to inject from a x86 injector into a x64 application. That is unsupportable, without access to ntdll functions.")));
}
};
let dll_is_x64 = self.get_is_dll_x64()?;
if dll_is_x64 && pid_is_under_wow {
crate::error!(
"Injecting a x64 dll, into a x86 exe is unsupported. Will not continue for now."
);
return Err(Error::Unsupported(Some(
"Injecting a x64 dll, into a x86 exe is unsupported.",
)));
} else if !dll_is_x64 && !pid_is_under_wow {
crate::error!("Injecting a x86 dll, into a x64 exe is unsupported. Could this case be supported? Send a PR, if you think, you can make this work! Will NOT abort, but expect the dll-injection to fail");
return Err(Error::Unsupported(Some(
"Injecting a x86 dll, into a x64 exe is unsupported.",
)));
}
#[cfg(not(feature = "ntdll"))]
if self_is_under_wow && !pid_is_under_wow {
return Err(Error::Unsupported(Some(
"Cannot inject into a x64 Application without ntdll access.",
)));
}
let entry_point = {
let (path, (base, _)) = get_module(crate::Data::Str(KERNEL32), &proc)?;
let path = if proc.is_under_wow()? {
path.replace(SYSTEM32, SYSWOW64)
} else {
path
};
base + get_dll_export(entry_fn, path)? as u64
};
crate::info!(
"Allocated {} Parameter at {:#x?}. fn ptr is {:#x} vs {:#x}",
entry_fn,
param,
entry_point,
entry_point as usize
);
#[cfg(feature = "x86tox64")]
{
let ntdll = self_is_under_wow && !pid_is_under_wow;
#[cfg(test)]
let ntdll = { test::FNS_M.with(|x| x.exec_fn_in_proc.get()) || ntdll };
if ntdll {
#[cfg(target_arch = "x86")]
let (r, t, _c) = {
let ntdll = ntdll::NTDLL::new()?;
let (path, base) = ntdll.get_ntdll_base_addr(pid_is_under_wow, &proc)?;
let rva = get_dll_export("RtlCreateUserThread", path)?;
let va = base + rva as u64;
unsafe {
crate::platforms::x86::exec(
va,
proc.get_proc(),
core::ptr::null_mut(),
0,
0,
0,
0,
entry_point as u64,
param as u64,
)?
}
};
#[cfg(not(target_arch = "x86"))]
let (r, t, _c) = {
let mut c = ntapi::ntapi_base::CLIENT_ID64 {
UniqueProcess: 0,
UniqueThread: 0,
};
let mut t = core::ptr::null_mut();
let r = unsafe {
ntapi::ntrtl::RtlCreateUserThread(
proc.get_proc(),
core::ptr::null_mut(),
0,
0,
0,
0,
core::mem::transmute(entry_point as usize),
param,
&mut t as winapi::shared::ntdef::PHANDLE,
core::mem::transmute(&mut c as ntapi::ntapi_base::PCLIENT_ID64),
)
};
(r, t, c)
};
match crate::error::Ntdll::new(r) {
crate::error::Ntdll::Error(v) => {
return Err(Error::Ntdll(v));
}
crate::error::Ntdll::Warning(v) => {
return Err(Error::Ntdll(v));
}
_ => {}
}
return if self.wait {
unsafe { thread::Thread::new(t)? }.wait_for_thread()
} else {
Ok(())
};
}
}
if !self_is_under_wow || pid_is_under_wow {
let mut thread_id: u32 = 0;
let thread = unsafe {
thread::Thread::new(CreateRemoteThread(
proc.get_proc(),
core::ptr::null_mut(),
0,
Some(core::mem::transmute(entry_point as usize)),
param,
0,
&mut thread_id as *mut u32,
))
}?;
let thread_id = thread_id;
crate::trace!("Thread is {:?} and thread id is {}", *thread, thread_id);
crate::info!("Waiting for DLL");
return if self.wait {
thread.wait_for_thread()
} else {
Ok(())
};
}
Err(crate::error::CustomError::NoViableInjector)?
}
fn find_pid_selector<F>(select: F) -> Result<Vec<u32>>
where
F: Fn(&PROCESSENTRY32W) -> bool,
{
let mut pids: Vec<DWORD> = Vec::new();
let snap_handle = unsafe { CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0) };
if snap_handle == INVALID_HANDLE_VALUE {
return Err(macros::err("CreateToolhelp32Snapshot"));
}
crate::trace!("Got Snapshot of processes");
let mut val = PROCESSENTRY32W {
dwSize: size_of::<PROCESSENTRY32W>() as u32,
cntUsage: 0, th32ProcessID: 0, th32DefaultHeapID: 0, th32ModuleID: 0, cntThreads: 0, th32ParentProcessID: 0, pcPriClassBase: 0, dwFlags: 0, szExeFile: [0u16; MAX_PATH],
};
let entry: LPPROCESSENTRY32W = &mut val as *mut PROCESSENTRY32W;
if unsafe { Process32FirstW(snap_handle, entry) == FALSE } {
return Err(macros::err("Process32FirstW"));
}
crate::trace!("Ran Process32FirstW");
loop {
if select(&val) {
pids.push(val.th32ProcessID);
}
if unsafe { Process32NextW(snap_handle, entry) == FALSE } {
break;
}
}
Ok(pids)
}
fn get_is_dll_x64(&self) -> Result<bool> {
let dll = read(&self.inj.dll)?;
return match pelite::pe64::PeFile::from_bytes(dll.as_slice()) {
Ok(_) => Ok(true),
Err(pelite::Error::PeMagic) => pelite::pe32::PeFile::from_bytes(dll.as_slice())
.map_err(|e| e.into())
.map(|_| false),
Err(err) => Err(err.into()),
};
}
}
fn get_module_in_pid<F, T>(pid: u32, predicate: F, snapshot_flags: Option<u32>) -> Result<T>
where
F: Fn(&MODULEENTRY32W) -> Option<T>,
{
let snap_modules = check_ptr!(
CreateToolhelp32Snapshot(
snapshot_flags.unwrap_or(TH32CS_SNAPMODULE32 | TH32CS_SNAPMODULE),
pid
),
|v| v == INVALID_HANDLE_VALUE
);
let mut module_entry = MODULEENTRY32W {
dwSize: size_of::<MODULEENTRY32W>() as u32, th32ModuleID: 1, th32ProcessID: 0, GlblcntUsage: 0, ProccntUsage: 0, modBaseAddr: core::ptr::null_mut(), modBaseSize: 0, hModule: core::ptr::null_mut(), szModule: [0; MAX_MODULE_NAME32 + 1], szExePath: [0; MAX_PATH], };
check_ptr!(
Module32FirstW(snap_modules, &mut module_entry as *mut MODULEENTRY32W),
|val| val == FALSE
);
loop {
if let Some(v) = predicate(&module_entry) {
check_ptr!(CloseHandle(snap_modules), |v| v == 0);
return Ok(v);
}
if unsafe { Module32NextW(snap_modules, &mut module_entry as *mut MODULEENTRY32W) } == FALSE
{
return Err(Error::from("Module32NextW"));
}
}
}
fn get_windir<'a>() -> Result<&'a alloc::string::String> {
static WINDIR: once_cell::race::OnceBox<alloc::string::String> =
once_cell::race::OnceBox::new();
let str = WINDIR.get_or_try_init(||{
let i=check_ptr!(GetSystemWindowsDirectoryW(core::ptr::null_mut(),0),|v|v==0);
let mut str_buf:alloc::vec::Vec<u16> = Vec::with_capacity( i as usize);
let i2=check_ptr!(GetSystemWindowsDirectoryW(str_buf.as_mut_ptr(),i),|v|v==0);
assert!(i2<=i,"GetSystemWindowsDirectoryA says, that {} bytes are needed, but then changed it's mind. Now {} bytes are needed.",i,i2);
unsafe{str_buf.set_len(i2 as usize)};
let string = str_from_wide_str(str_buf.as_slice())?;
crate::debug!("Windir is {},{},{}",string,i,i2);
Ok(alloc::boxed::Box::new(string))
})?;
crate::debug!("Windir is '{}'", str);
Ok(str)
}
///Takes in a Name. From that it returns a matcher
///Takes a Selector, and returns a type, for use with get_module_in_proc and get_module_in_pid
///name specifies, what module to look for.
///f processes the other input from other functions
//todo: does this bring a performance benefit?
fn predicate<T, F, O, C>(f: T, cmp: C) -> impl Fn(F, &[u16]) -> Option<(String, O)>
where
T: Fn(F) -> O,
C: Fn(&String) -> bool,
{
move |i2, v| match str_from_wide_str(crate::trim_wide_str::<true>(v)) {
Ok(s) => {
if cmp(&s) {
Some((s, f(i2)))
} else {
None
}
}
Err(_) => None,
}
}
///Gets the base address of where a dll is loaded within a process.
///The dll is identified by name. name is checked against the whole file name.
///if the process has a pseudo-handle, the ntdll methods will not run.
///
///The return value is (dll name, (dll base address, dll handle))
//This return type is getting out of hand. Mayebe consider a struct for this?
fn get_module(name: crate::Data, proc: &Process) -> Result<(String, (u64, Option<LPVOID>))> {
let cmp = cmp(name);
let ntdll = !process::Process::self_proc().is_under_wow()? || proc.is_under_wow()?;
#[cfg(test)]
let ntdll = { test::FNS_M.with(|x| x.get_module.get()) || ntdll };
if ntdll {
match get_module_in_pid(
proc.get_pid(),
|m| {
predicate(
|m: &MODULEENTRY32W| (m.modBaseAddr as u64, Some(m.hModule as LPVOID)),
|x| (&cmp)(crate::Data::Str(x.as_str())),
)(m, &m.szExePath)
},
None,
) {
Ok(r) => return Ok(r),
//This should return, if ntdll is disabled. If ntdll is enabled, this gets discarded
#[cfg_attr(feature = "ntdll", allow(unused_variables))]
Err(v) =>
{
#[cfg(not(feature = "ntdll"))]
return Err(v)
}
}
} else {
crate::warn!("We are injecting from a x86 injector into a x64 target executable. ");
#[cfg(not(feature = "ntdll"))]
return Err(Error::Unsupported(Some("No Ntdll support enabled. Cannot get module. Target process is x64, but we are compiled as x86.")));
}
#[cfg(feature = "ntdll")]
{
crate::info!("Trying get_module_in_proc as fallback method.");
unsafe {
let ntdll = ntdll::NTDLL::new()?;
return ntdll.get_module_in_proc(
proc,
predicate(
|w: pelite::Wrap<LDR_DATA_TABLE_ENTRY32, ntdll::LDR_DATA_TABLE_ENTRY64>| match w
{
//todo: the dll address is not garunteed to be the dll handle, but that seems to be the case.
pelite::Wrap::T32(w) => (w.DllBase as u64, Some(w.DllBase as LPVOID)),
pelite::Wrap::T64(w) => (w.DllBase as u64, Some(w.DllBase as LPVOID)),
},
|x| (&cmp)(crate::Data::Str(x.as_str())),
),
);
}
}
}
///Gets a function export from the dll at the specified path (even under WOW), and return the rva, if found.
///
///This gets the Relative Virtual Address (rva) of the function name, from a pe-file.
///This function will make sure, that all requests, that according to path should go to %windir%/System32, actually go there.
///If you want to get an export from a 32-bit dll under 64-bit windows specify %windir%/SysWOW64.
fn get_dll_export(name: &str, path: alloc::string::String) -> Result<u32> {
let path = if process::Process::self_proc().is_under_wow()? {
let str = get_windir()?.clone();
path.replace(
(str.clone() + "\\System32").as_str(),
(str + "\\Sysnative").as_str(),
)
} else {
path
};
log::trace!(r#"Path is "{}""#, path);
let path = canonicalize(&crate::Data::Str(path.as_str()))?.0;
log::trace!(r#"Canonical Path is "{}""#, path);
debug_assert!(
canonicalize(&crate::Data::Str(path.as_str())).is_ok(),
"parsing {} failed",
path,
);
let k32 = read(&crate::Data::Str(path.as_str()))?;
log::trace!("{:#x?}", &k32.as_slice()[0..32]);
let dll_parsed = pelite::PeFile::from_bytes(k32.as_slice())?;
let rva = dll_parsed.get_export_by_name(name)?.symbol().unwrap();
crate::trace!("Found {} at rva:{} in dll {}", name, rva, path);
Ok(rva)
}
#[cfg(test)]
pub mod test {
extern crate std;
use alloc::string::ToString;
use std::prelude::*;
use std::println;
use std::vec::Vec;
use crate::error::Error;
use crate::platforms::windows::InjectWin;
use crate::{Inject, Result};
use std::cell::Cell;
use std::ffi::OsString;
use std::os::windows::ffi::OsStrExt;
use std::os::windows::io::AsRawHandle;
use std::os::windows::process::CommandExt;
use std::process::Child;
use std::thread::sleep;
use std::time::Duration;
use winapi::um::errhandlingapi::GetLastError;
use winapi::um::libloaderapi::{FreeLibrary, LoadLibraryA};
use winapi::um::tlhelp32::MODULEENTRY32W;
use winapi::um::winbase::CREATE_NEW_CONSOLE;
use winapi::um::winnt::PROCESS_ALL_ACCESS;
std::thread_local! {
pub(in super) static FNS_M:FNS=FNS::default();
}
#[derive(Debug)]
pub(super) struct FNS {
pub get_module: Cell<bool>,
#[cfg(feature = "ntdll")]
pub exec_fn_in_proc: Cell<bool>,
}
impl Default for FNS {
fn default() -> Self {
FNS {
get_module: Cell::new(false),
#[cfg(feature = "ntdll")]
exec_fn_in_proc: Cell::new(false),
}
}
}
///This will create a new cmd process.
///You MUST bind the Process to something else than _ (even _a is apparently fine?).
pub fn create_cmd() -> (Child, super::process::Process) {
#[cfg(not(target_pointer_width = "32"))]
let path = "cmd.exe";
#[cfg(target_pointer_width = "32")]
let path = {
let mut path = super::get_windir().unwrap().clone();
path.push_str(r"\Sysnative\cmd.exe");
path
};
let c = std::process::Command::new(path)
.creation_flags(CREATE_NEW_CONSOLE)
.spawn()
.unwrap();
sleep(Duration::from_millis(100)); //Let the process init.
let proc = unsafe {
super::process::Process::from_raw_parts(
c.as_raw_handle() as usize,
c.id(),
CREATE_NEW_CONSOLE | PROCESS_ALL_ACCESS,
)
};
(c, proc)
}
#[test]
///Tests, that create_cmd does not panic, and that the drop will work.
///The decompiler tells some interesting stuff. I would rather have it tested here.
fn test_create_cmd() {
//we need to bind process to _a here, so that drop doesn't get called instantly.
//Dropping Process will close the child handle, which isn't what we want
let (mut c, _a) = create_cmd();
c.kill().unwrap();
}
#[test]
fn canonicalise() -> Result<()> {
simple_logger::init().ok();
let windir = super::get_windir()?;
let mut path = windir.clone();
const cmd_path: &'static str = if cfg!(target_pointer_width = "32")
//On 32-bit wow redirects the path
{
r"\SysWOW64\cmd.exe"
} else {
r"\System32\cmd.exe"
};
path.push_str(cmd_path);
log::info!("{}", path);
//non-std
{
let (fp, lps) = super::canonicalize(&crate::Data::Str(path.as_str()))?;
let fp = fp.trim_start_matches(r"\\?\");
assert_eq!(fp, path);
assert_eq!(lps, Some("cmd.exe".to_string()));
}
//std
#[cfg(all(feature = "std", test))]
{
let (fp, lps) = super::canonicalize(&crate::Data::Path(std::path::Path::new(&path)))?;
assert_eq!(lps, Some("cmd.exe".to_string()));
let fp = fp.trim_start_matches(r"\\?\");
assert_eq!(fp, path);
}
Ok(())
}
#[test]
fn read() -> Result<()> {
simple_logger::SimpleLogger::new().init().ok();
let windir = super::get_windir()?;
let mut path = windir.clone();
path.push_str(r"\System32\cmd.exe");
log::info!("{}", path);
let cstd = std::fs::read(&path).unwrap();
log::info!("STD read size is {}", cstd.len());
let c = super::read(&crate::Data::Str(path.as_str()))?;
log::info!("self read size is {}", c.len());
//assert_eq!(c,cstd,"std and self read result is not same");
assert!(c == cstd);
Ok(())
}
#[test]
fn get_windir() -> Result<()> {
let r = super::get_windir();
assert!(r.is_ok(), "get_windir returned Err({})", r.unwrap_err());
let r = r.unwrap();
let env_var_windir = std::env::var("WINDIR").unwrap();
assert_eq!(r, &env_var_windir);
let f = std::fs::read_dir(r);
assert!(
f.is_ok(),
"Couldn't read Windows dir. Error is {}",
f.unwrap_err()
);
//can't do too much in this test sadly, since we can't assume a "normal" windows environment.
//Testing if the string returned from get_windir is the most I can do here.
Ok(())
}
#[test]
fn get_dll_export() -> Result<()> {
let mut path = std::path::PathBuf::from(super::get_windir()?);
path.push("System32"); //Cannot assume, that everyone has WOW installed.
{
let mut ntdll = path.clone();
ntdll.push("ntdll.dll");
let r =
super::get_dll_export("RtlCreateUserThread", ntdll.to_str().unwrap().to_string());
assert!(r.is_ok(), "get_dll_export returned err:{}", r.unwrap_err());
r?;
}
{
let mut kernel32 = path;
kernel32.push("kernel32.dll");
let r = super::get_dll_export("LoadLibraryW", kernel32.to_str().unwrap().to_string());
assert!(r.is_ok(), "get_dll_export returned err:{}", r.unwrap_err());
r?;
}
Ok(())
}
#[test]
fn str_from_wide_str() -> Result<()> {
//test empty string
assert_eq!(
super::str_from_wide_str(std::vec![].as_slice())?,
"".to_string()
);
//Test just about every special char I could think of.
let wide_str: Vec<u16> = OsString::from(crate::test::STR.to_string())
.as_os_str()
.encode_wide()
.collect();
assert_eq!(
super::str_from_wide_str(wide_str.as_slice())?,
crate::test::STR.to_string()
);
Ok(())
}
#[test]
fn predicate() {
//If nothing matches, we should get NONE back
assert_eq!(super::predicate(|_| 0, |_| false)((), &[]), None);
//If something matches, we should get a result
assert_eq!(
super::predicate(|_| 0, |_| true)((), &[]),
Some(("".to_string(), 0))
);
}
#[test]
fn get_module_in_pid() -> Result<()> {
let test = |id: u32| {
super::get_module_in_pid(
id,
|m| {
super::predicate(
|m: &MODULEENTRY32W| m.modBaseAddr as u64,
|x| super::cmp(crate::Data::Str("ntdll.dll"))(crate::Data::Str(x.as_str())),
)(m, &m.szModule)
},
None,
)
};
//test self
{
let h = unsafe { LoadLibraryA(b"ntdll.dll\0".as_ptr() as *mut i8) };
assert!(!h.is_null(), "Couldn't load ntdll into our current process");
let (_, n) = test(std::process::id())?;
if n != h as u64 {
println!("Base Address!=LoadLibraryA, {}!={}", n, h as u64)
};
let r = unsafe { FreeLibrary(h) };
assert_ne!(r, 0, "FreeLibrary failed, because {}", unsafe {
GetLastError()
});
}
//test other
{
let (mut c, p) = create_cmd();
if p.is_under_wow()? || !super::process::Process::self_proc().is_under_wow()? {
test(c.id())?;
}
c.kill().unwrap();
}
Ok(())
}
#[test]
fn get_module() -> Result<()> {
//self test
{
let r = super::get_module(
crate::Data::Str("ntdll.dll"),
&super::process::Process::self_proc(),
);
assert!(r.is_ok(), "normal self get_module err:{}", r.unwrap_err());
}
let (mut c, cp) = create_cmd();
//other test
{
let r = super::get_module(crate::Data::Str("ntdll.dll"), &cp);
if cfg!(target_pointer_width = "64") || cfg!(feature = "ntdll") {
assert!(r.is_ok(), "normal other get_module err:{}", r.unwrap_err());
} else {
assert_eq!(r.unwrap_err(),Error::Unsupported(Some("No Ntdll support enabled. Cannot get module. Target process is x64, but we are compiled as x86.")));
}
}
// #[cfg(feature = "ntdll")]
#[cfg(target_pointer_width = "0")] //FIXME: Fix ntdll stuff
{
let proc = super::process::Process::new(std::process::id(), PROCESS_ALL_ACCESS)?;
FNS_M.with(|x| x.get_module.set(true));
let r = super::get_module(crate::Data::Str("KERNEL32.DLL"), &proc);
let r1 = super::get_module(crate::Data::Str("KERNEL32.DLL"), &cp);
FNS_M.with(|x| x.get_module.set(false));
assert!(r.is_ok(), "ntdll self get_module:{}", r.unwrap_err());
assert!(r1.is_ok(), "ntdll other get_module:{}", r.unwrap_err());
}
c.kill().unwrap();
Ok(())
}
#[test]
fn find_pid() -> Result<()> {
let exe = std::env::current_exe().map_err(|_| crate::error::Error::Unsupported(None))?;
#[cfg(feature = "std")]
{
let i = InjectWin::find_pid(crate::Data::Path(exe.as_path()));
assert!(i.is_ok(), "{}", i.unwrap_err());
assert!(
i?.contains(&std::process::id()),
"The result did not contain our current process id."
);
}
let i = InjectWin::find_pid(crate::Data::Str(exe.as_os_str().to_str().unwrap()));
assert!(i.is_ok(), "{}", i.unwrap_err());
assert!(
i?.contains(&std::process::id()),
"The result did not contain our current process id."
);
Ok(())
}
}