Skip to content

Commit

Permalink
ditch non-windows dll proxying and split the code
Browse files Browse the repository at this point in the history
Signed-off-by: Dimitris Zervas <[email protected]>
  • Loading branch information
dzervas committed Apr 13, 2024
1 parent 7c8105f commit a5a0153
Show file tree
Hide file tree
Showing 6 changed files with 62 additions and 113 deletions.
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ ctor = "0.2.7"

[build-dependencies]
goblin = "0.8.0"
build-target = "0.4"

[dev-dependencies]
pretty_assertions = "1.4.0"
# mylib = { path = "tests/mylib" }
80 changes: 11 additions & 69 deletions build.rs
Original file line number Diff line number Diff line change
@@ -1,98 +1,40 @@
use std::env;
use std::io::Write;
use std::fs::File;
use std::path::Path;

fn main() {
println!("cargo:rerun-if-env-changed=FRIDA_CODE");
println!("cargo:rerun-if-env-changed=DLL_PROXY");

let Ok(lib_path) = env::var("DLL_PROXY") else {
println!("cargo:warning=No DLL_PROXY set, the resulting library has to be manually injected or compiled into the target binary");
println!("cargo:warning=No DLL_PROXY set, the resulting library has to be manually injected or compiled into the target binary/process");
return;
};

if build_target::target_os() != Ok(build_target::Os::Windows) {
panic!("Dll proxying mode is only supported on Windows.");
}

use goblin::Object;
println!("cargo:rerun-if-changed={}", &lib_path);

let path = std::path::Path::new(&lib_path);
let path = Path::new(&lib_path);
let lib_filename = path.file_name().unwrap().to_str().unwrap();

let lib_bytes = std::fs::read(path).expect(format!("Failed to open given library file {}", &lib_filename).as_str());
let object = Object::parse(&lib_bytes).expect(format!("Failed to parse given libary file {}", &lib_filename).as_str());

let (exports, lib_name): (Vec<&str>, String) = match object {
#[cfg(target_os = "windows")]
Object::PE(o) => {
(o.exports
.iter()
.map(|e| e.name.unwrap())
.collect(),
o.name.expect("Couldn't read the name of the DLL. Is it a .NET DLL? It's not supported").replace(".dll", ""))
}
#[cfg(target_os = "linux")]
Object::Elf(o) => {
(o.dynsyms
.iter()
.filter(|e| e.is_function() && !e.is_import())
.map(|e| o.dynstrtab.get_at(e.st_name).unwrap())
.collect(),
// o.soname.expect("Couldn't read the name of the SO.").replace(".so", ""))
lib_filename.replace(".so", ""))
},
#[cfg(target_os = "darwin")]
Object::Mach(goblin::mach::Mach::Binary(o)) => {
(o.dynsyms
.iter()
.filter(|e| e.is_function() && !e.is_import())
.map(|e| o.dynstrtab.get_at(e.st_name).unwrap())
.collect(),
o.name.expect("Couldn't read the name of the DLL. Is it a .NET DLL? It's not supported").replace(".dll", ""))
},
_ => {
println!("Only PE (.dll) and ELF (.so) files are supported in their respective target platforms.");
std::process::exit(1);
},
let Object::PE(pe) = object else {
panic!("Only PE (.dll) files are supported in this mode.");
};

#[cfg(target_os = "windows")]
let exports: Vec<&str> = pe.exports.iter().map(|e| e.name.unwrap()).collect();
let lib_name = pe.name.expect("Couldn't read the name of the DLL. Is it a .NET DLL? It's not supported").replace(".dll", "");

for e in exports.iter() {
println!("cargo:warning=Exported function: {} => {}-orig.{}", e, lib_name, e);
println!("cargo:rustc-link-arg=/export:{}={}-orig.{}", e, lib_name, e);
}

#[cfg(unix)]
{
let symbols_path = Path::new(env!("CARGO_MANIFEST_DIR")).join("src").join("symbols.rs");
let mut symbols = File::create(&symbols_path).unwrap();
println!("cargo:rerun-if-changed={:?}", symbols_path);
println!("cargo:rustc-cfg=symbols");

let lib_name = if lib_name.starts_with("lib") {
lib_name.replacen("lib", "", 1)
} else {
lib_name.clone()
};

writeln!(symbols, "#![allow(dead_code)]").unwrap();

for e in exports.iter() {
writeln!(symbols, "#[no_mangle]").unwrap();
writeln!(symbols, r#"pub unsafe extern "C" fn {e}() {{ original::{e}() }}"#).unwrap();
}

writeln!(symbols, "pub mod original {{").unwrap();
writeln!(symbols, "\t#[link(name = \"{}\")]", lib_name).unwrap();
writeln!(symbols, "\textern {{").unwrap();
for e in exports.iter() {
println!("cargo:warning=Exported function: {}", e);
// writeln!(symbols, "\t#[no_mangle]").unwrap();
writeln!(symbols, "\t\tpub fn {e}();").unwrap();
}
writeln!(symbols, "\t}}").unwrap();
writeln!(symbols, "}}").unwrap();
}

println!("cargo:warning=Expected library name: {}-orig.dll", lib_name);
println!("cargo:rustc-env=LIB_NAME={}-orig.dll", lib_name);
}
50 changes: 7 additions & 43 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,63 +2,24 @@ pub mod injector;
#[cfg(feature = "frida")]
pub mod frida_handler;

#[cfg(symbols)]
pub mod symbols;
#[cfg(symbols)]
pub use symbols::*;

pub use injector::attach_self;

#[cfg(all(unix, not(test)))]
use ctor::ctor;

// During testing we compile a debug binary without `test`.
// Enabling `ctor` during testing would hook the test runner and break it.
#[cfg(all(unix, not(test)))]
#[ctor]
fn _start() {
println!("[+] frida-deepfreeze-rs library injected");
attach_self();
}

// For some reason ctor doesn't work on Windows - it hangs the process
// during DeviceManager::obtain. DllMain works fine though.
#[cfg(all(windows, not(test)))]
use std::ffi::c_void;
#[cfg(all(windows, not(test)))]
use winapi::um::winnt::DLL_PROCESS_ATTACH;
pub mod loader_unix;

#[cfg(all(windows, not(test)))]
use winapi::um::libloaderapi::LoadLibraryA;

pub mod loader_windows;
#[cfg(all(windows, not(test)))]
#[no_mangle]
#[allow(non_snake_case, unused_variables)]
pub extern "system" fn DllMain(dll_module: *mut c_void, call_reason: u32, _: *mut ()) -> bool {
match call_reason {
DLL_PROCESS_ATTACH => {
println!("[+] frida-deepfreeze-rs DLL injected");

if let Some(lib_name) = option_env!("LIB_NAME") {
unsafe { LoadLibraryA(lib_name.as_ptr() as *const i8); }
println!("[+] Original DLL {} loaded", lib_name);
}

attach_self();
}
// Maybe we should detach? Is it useful?
_ => ()
}

true
}
pub use loader_windows::DllMain;

#[cfg(test)]
mod tests {
use pretty_assertions::assert_eq;
use std::process::Command;
use std::fs;

#[allow(dead_code)]
fn get_lib_name(name: &str) -> String {
#[cfg(target_os = "windows")]
return format!("{}.dll", name);
Expand Down Expand Up @@ -103,7 +64,10 @@ mod tests {
}

#[test]
#[cfg(windows)]
fn test_frida_dll_proxy() {
use std::fs;

let mylib_name = get_lib_name("mylib");
fs::remove_file(format!("target/test_frida_dll_proxy/debug/deps/{}", mylib_name)).unwrap_or_else(|_| ());

Expand Down
9 changes: 9 additions & 0 deletions src/loader_unix.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
use ctor::ctor;

use crate::injector::attach_self;

#[ctor]
fn _start() {
println!("[+] frida-deepfreeze-rs library injected");
attach_self();
}
27 changes: 27 additions & 0 deletions src/loader_windows.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
use std::ffi::c_void;
use winapi::um::winnt::DLL_PROCESS_ATTACH;
use winapi::um::libloaderapi::LoadLibraryA;

// For some reason ctor doesn't work on Windows - it hangs the process
// during DeviceManager::obtain. DllMain works fine though.
// Would be nice to have a single entry point for all platforms.
#[no_mangle]
#[allow(non_snake_case, unused_variables)]
pub extern "system" fn DllMain(dll_module: *mut c_void, call_reason: u32, _: *mut ()) -> bool {
match call_reason {
DLL_PROCESS_ATTACH => {
println!("[+] frida-deepfreeze-rs DLL injected");

if let Some(lib_name) = option_env!("LIB_NAME") {
unsafe { LoadLibraryA(lib_name.as_ptr() as *const i8); }
println!("[+] Original DLL {} loaded", lib_name);
}

attach_self();
}
// Maybe we should detach? Is it useful?
_ => ()
}

true
}

0 comments on commit a5a0153

Please sign in to comment.