diff --git a/Cargo.toml b/Cargo.toml index 042cc6c..d3f8afb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sharun" -version = "0.0.3" +version = "0.0.4" readme = "README.md" license = "MIT" repository = "https://github.com/VHSgunzo/sharun" diff --git a/README.md b/README.md index 1c8beb6..1fabd71 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # sharun -Run dynamically linked ELF binaries everywhere (musl and glibc are supported) +Run dynamically linked ELF binaries everywhere (musl and glibc are supported). +* This works with [userland-execve](https://github.com/io12/userland-execve-rust) by mapping the interpreter (such as ld-linux-x86-64.so.2) into memory, creating a stack for it (containing the auxiliary vector, arguments, and environment variables), and then jumping to the entry point with the new stack. +* [lib4bin](https://github.com/VHSgunzo/sharun/blob/main/lib4bin) pulls out the binary file and all the libraries on which it depends, strip it (you can disable it with `STRIP=0` env var, see [lib4bin](https://github.com/VHSgunzo/sharun/blob/main/lib4bin#L15)) and forms the `bin`, `shared/{bin,lib,lib32}` directories (see [screenshots](https://github.com/VHSgunzo/sharun?tab=readme-ov-file#screenshots)) and generate a file `shared/{lib,lib32}/lib.path` with a list of all directories that contain libraries for pass it to interpreter `--library-path`. The paths in this file are specified on a new line with a `+` at the beginning and relative to the directory in which it is located. ## Supported architectures: * aarch64 @@ -34,7 +36,7 @@ cargo build --release | -h, --help Print help | [ Environments ]: -| SHARUN_LDNAME=ld.so Specifies the name of the linker +| SHARUN_LDNAME=ld.so Specifies the name of the interpreter ``` ## Examples: @@ -42,15 +44,26 @@ cargo build --release # create a directory and cd mkdir test && cd test +# and copy 'sharun' to this directory +cp ../target/x86_64-unknown-linux-musl/release/sharun . + # run lib4bin with the paths to the binary files that you want to make portable ../lib4bin /bin/{curl,bash,ls} -# and copy sharun to this directory -cp ../target/x86_64-unknown-linux-musl/release/sharun . +# or for correct /proc/self/exe you can use HARD_LINKS=1 +HARD_LINKS=1 ../lib4bin /bin/{curl,bash,ls} +# this ^ will create hard links to 'sharun' in the 'bin' directory # now you can move 'test' dir to other linux system and run binaries from the 'bin' dir ./bin/ls -lha -# or specify them as an argument to sharun +# or specify them as an argument to 'sharun' ./sharun ls -lha ``` + +# Screenshots: +![alt text](img/image.png) + +## References +* [userland-execve](https://crates.io/crates/userland-execve) +* https://brioche.dev/blog/portable-dynamically-linked-packages-on-linux \ No newline at end of file diff --git a/img/image.png b/img/image.png new file mode 100644 index 0000000..a51bef1 Binary files /dev/null and b/img/image.png differ diff --git a/lib4bin b/lib4bin index 85c0fd2..00b51f1 100755 --- a/lib4bin +++ b/lib4bin @@ -23,6 +23,20 @@ CREATE_LINKS=${CREATE_LINKS:=1} #PATCH_RPATH=1 #PATCH_INTERPRETER=1 +check_deps() { + local ret=0 + for bin in file strip patchelf find + do ! command -v $bin &>/dev/null && \ + echo -e "$RED[ ERROR ]: $BLUE[$bin]$YELLOW not found!$RESETCOLOR" && \ + ret=1 + done + if [ "$ret" != 0 ] + then + echo -e "${GREEN}You need to install lib4bin dependencies: ${BLUE}file binutils patchelf findutils$RESETCOLOR" + exit 1 + fi +} + try_strip() { [ "$STRIP" == 1 ] && \ strip -s -R .comment --strip-unneeded "$1" @@ -64,6 +78,8 @@ repath_needed_libs() { done } +check_deps + #IFS=$'\n' if [ ! -n "$BINARY_LIST" ] then diff --git a/src/main.rs b/src/main.rs index 04c5f2a..ce5a50a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,40 +1,46 @@ use std::{ - fs::File, - io::Read, - {env, fs}, - path::Path, - str::FromStr, + env, ffi::CString, + str::FromStr, process::exit, - collections::HashSet + collections::HashSet, + path::{Path, PathBuf}, + io::{Read, Result, Error}, + fs::{File, write, read_to_string} }; use walkdir::WalkDir; const SHARUN_NAME: &str = env!("CARGO_PKG_NAME"); -const LINKER_NAME: &str = "ld-linux-x86-64.so.2"; -fn get_linker_name(library_path: &str) -> String { - #[cfg(target_arch = "x86_64")] // target x86_64-unknown-linux-musl - let linkers = vec![ - "ld-linux-x86-64.so.2", - "ld-musl-x86_64.so.1", - "ld-linux.so.2", - ]; - #[cfg(target_arch = "aarch64")] // target aarch64-unknown-linux-musl - let linkers = vec![ - "ld-linux-aarch64.so.1", - "ld-musl-aarch64.so.1", - ]; - for linker in linkers { - let linker_path = Path::new(library_path).join(linker); - if linker_path.exists() { - return linker_path.file_name().unwrap().to_str().unwrap().to_string() +fn get_interpreter(library_path: &str) -> Result { + let mut interpreters = Vec::new(); + if let Ok(ldname) = env::var("SHARUN_LDNAME") { + if !ldname.is_empty() { + interpreters.push(ldname) + } + } else { + #[cfg(target_arch = "x86_64")] // target x86_64-unknown-linux-musl + interpreters.append(&mut vec![ + "ld-linux-x86-64.so.2".into(), + "ld-musl-x86_64.so.1".into(), + "ld-linux.so.2".into() + ]); + #[cfg(target_arch = "aarch64")] // target aarch64-unknown-linux-musl + interpreters.append(&mut vec![ + "ld-linux-aarch64.so.1".into(), + "ld-musl-aarch64.so.1".into() + ]); + } + for interpreter in interpreters { + let interpreter_path = Path::new(library_path).join(interpreter); + if interpreter_path.exists() { + return Ok(interpreter_path) } } - LINKER_NAME.to_string() + Err(Error::last_os_error()) } fn realpath(path: &str) -> String { @@ -51,7 +57,7 @@ fn is_file(path: &str) -> bool { path.is_file() } -fn is_elf32(file_path: &str) -> std::io::Result { +fn is_elf32(file_path: &str) -> Result { let mut file = File::open(file_path)?; let mut buff = [0u8; 5]; file.read_exact(&mut buff)?; @@ -87,7 +93,7 @@ fn gen_library_path(library_path: &mut String) -> i32 { library_path.push(':'); library_path.push_str(&new_paths.join(":")) } - if let Err(err) = fs::write(&lib_path_file, &library_path + if let Err(err) = write(&lib_path_file, &library_path .replace(":", "\n") .replace(&old_library_path, "+") ) { @@ -119,12 +125,14 @@ fn print_usage() { | -h, --help Print help | [ Environments ]: - | SHARUN_LDNAME=ld.so Specifies the name of the linker", + | SHARUN_LDNAME=ld.so Specifies the name of the interpreter", env!("CARGO_PKG_DESCRIPTION")))); } fn main() { let sharun = env::current_exe().unwrap(); + let mut exec_args: Vec = env::args().collect(); + let mut sharun_dir = sharun.parent().unwrap().to_str().unwrap().to_string(); let lower_dir = format!("{sharun_dir}/../"); if basename(&sharun_dir) == "bin" && @@ -132,8 +140,6 @@ fn main() { sharun_dir = realpath(&lower_dir) } - let mut exec_args: Vec = env::args().collect(); - let shared_dir = format!("{sharun_dir}/shared"); let shared_bin = format!("{shared_dir}/bin"); let shared_lib = format!("{shared_dir}/lib"); @@ -186,18 +192,14 @@ fn main() { library_path = shared_lib } - let linker_name = env::var("SHARUN_LDNAME") - .unwrap_or(get_linker_name(&library_path)); - let linker = &format!("{library_path}/{linker_name}"); - - if !Path::new(linker).exists() { - eprintln!("Linker not found: {linker}"); + let interpreter = get_interpreter(&library_path).unwrap_or_else(|_|{ + eprintln!("The interpreter was not found!"); exit(1) - } + }); let lib_path_file = format!("{library_path}/lib.path"); if Path::new(&lib_path_file).exists() { - library_path = fs::read_to_string(lib_path_file).unwrap().trim() + library_path = read_to_string(lib_path_file).unwrap().trim() .replace("\n", ":") .replace("+", &library_path) } else { @@ -209,19 +211,19 @@ fn main() { format!("{}={}", key, value) ).unwrap()).collect(); - let mut linker_args = vec![ - CString::from_str(linker).unwrap(), + let mut interpreter_args = vec![ + CString::from_str(&interpreter.to_string_lossy()).unwrap(), CString::new("--library-path").unwrap(), CString::new(library_path).unwrap(), CString::new(bin).unwrap() ]; for arg in exec_args { - linker_args.push(CString::from_str(&arg).unwrap()) + interpreter_args.push(CString::from_str(&arg).unwrap()) } userland_execve::exec( - Path::new(linker), - &linker_args, + interpreter.as_path(), + &interpreter_args, &envs, ) }