diff --git a/docs/_docs/user-guide/eldritch.md b/docs/_docs/user-guide/eldritch.md index 0afae7653..b48cffd69 100644 --- a/docs/_docs/user-guide/eldritch.md +++ b/docs/_docs/user-guide/eldritch.md @@ -397,6 +397,19 @@ file.read("/etc/*ssh*") # Read the contents of all files that have `ssh` in the file.read("\\\\127.0.0.1\\c$\\Windows\\Temp\\metadata.yml") # Read file over Windows UNC ``` +### file.read_binary + +`file.read(path: str) -> List` + +The file.read_binary method will read the contents of a file, returning as a list of bytes. If the file or directory doesn't exist the method will error to avoid this ensure the file exists, and you have permission to read it. +This function supports globbing with `*` for example: + +```python +file.read_binary("/home/*/.bash_history") # Read all files called .bash_history in sub dirs of `/home/` +file.read_binary("/etc/*ssh*") # Read the contents of all files that have `ssh` in the name. Will error if a dir is found. +file.read_binary("\\\\127.0.0.1\\c$\\Windows\\Temp\\metadata.yml") # Read file over Windows UNC +``` + ### file.remove `file.remove(path: str) -> None` diff --git a/implants/lib/eldritch/src/file/mod.rs b/implants/lib/eldritch/src/file/mod.rs index a3134258d..38af506c5 100644 --- a/implants/lib/eldritch/src/file/mod.rs +++ b/implants/lib/eldritch/src/file/mod.rs @@ -10,6 +10,7 @@ mod list_impl; mod mkdir_impl; mod moveto_impl; mod parent_dir_impl; +mod read_binary_impl; mod read_impl; mod remove_impl; mod replace_all_impl; @@ -121,6 +122,11 @@ fn methods(builder: &mut MethodsBuilder) { read_impl::read(path) } + #[allow(unused_variables)] + fn read_binary(this: &FileLibrary, path: String) -> anyhow::Result> { + read_binary_impl::read_binary(path) + } + #[allow(unused_variables)] fn remove(this: &FileLibrary, path: String) -> anyhow::Result { remove_impl::remove(path)?; diff --git a/implants/lib/eldritch/src/file/read_binary_impl.rs b/implants/lib/eldritch/src/file/read_binary_impl.rs new file mode 100644 index 000000000..ac18870d9 --- /dev/null +++ b/implants/lib/eldritch/src/file/read_binary_impl.rs @@ -0,0 +1,116 @@ +use anyhow::Result; +use glob::{glob, GlobError}; +use std::{fs, path::PathBuf}; + +pub fn read_binary(path: String) -> Result> { + let mut res: Vec = Vec::new(); + let glob_res = glob(&path)?.collect::>>(); + if glob_res.is_empty() { + return Err(anyhow::anyhow!( + "file.read_binary: pattern {} found no results", + path, + )); + } + + for entry in glob_res { + match entry { + Ok(entry_path) => { + let data = fs::read(entry_path)?; + res.extend(data); + } + Err(_err) => { + #[cfg(debug_assertions)] + log::debug!("Failed to parse glob {}\n{}", path, _err); + } + } + } + Ok(res.into_iter().map(|b| b as u32).collect()) +} + +#[cfg(test)] +mod tests { + use super::*; + use std::{fs::File, io::prelude::*}; + use tempfile::{tempdir, NamedTempFile}; + + const HELLO_WORLD_STR_BYTES: &[u8; 14] = b"Hello, world!\n"; + const HELLO_WORLD_BYTES: [u32; 14] = [ + 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x2c, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x21, 0x0a, + ]; + + #[test] + fn test_read_simple() -> anyhow::Result<()> { + // Create file + let mut tmp_file = NamedTempFile::new()?; + let path = String::from(tmp_file.path().to_str().unwrap()); + + // Write to New File + tmp_file.write_all(HELLO_WORLD_STR_BYTES)?; + + // Run our code + let res = read_binary(path)?; + // Verify output + assert_eq!(res, Vec::from(HELLO_WORLD_BYTES)); + Ok(()) + } + #[test] + fn test_read_large() -> anyhow::Result<()> { + // Create file + let mut tmp_file = NamedTempFile::new()?; + let path = String::from(tmp_file.path().to_str().unwrap()); + + // Write to New File + for _ in 0..256 { + tmp_file.write_all(HELLO_WORLD_STR_BYTES)?; + } + + // Run our code + let res = read_binary(path)?; + let expected = Vec::from(HELLO_WORLD_BYTES.repeat(256)); + // Verify output + assert_eq!(res, expected); + Ok(()) + } + #[test] + fn test_read_nonexistent() -> anyhow::Result<()> { + // Create file + let tmp_file = NamedTempFile::new()?; + let path = String::from(tmp_file.path().to_str().unwrap()); + tmp_file.close()?; + + let res = read_binary(path); + assert!(res.is_err()); + Ok(()) + } + + fn contains_slice(vec: &[T], slice: &[T]) -> bool { + vec.windows(slice.len()).any(|window| window == slice) + } + + #[test] + fn test_read_glob() -> anyhow::Result<()> { + // Create file + let tmp_dir = tempdir()?; + let matched_files = ["thesshfile", "anothersshfile"]; + let unmatched_files = ["noswordshere"]; + let tmp_path = tmp_dir.into_path(); + for f in matched_files { + let mut file = File::create(tmp_path.clone().join(f).clone())?; + file.write_all(b"Hello\n")?; + } + for f in unmatched_files { + let mut file = File::create(tmp_path.clone().join(f))?; + file.write_all(b"Bye")?; + } + + let path = String::from(tmp_path.clone().join("*ssh*").to_str().unwrap()); + let res = read_binary(path)?; + + assert!(!contains_slice(&res, &[0x42, 0x79, 0x65])); + assert_eq!( + res, + Vec::from([0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x0a, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x0a]) + ); + Ok(()) + } +} diff --git a/implants/lib/eldritch/src/runtime/mod.rs b/implants/lib/eldritch/src/runtime/mod.rs index c6d5f99ed..afd3be8bb 100644 --- a/implants/lib/eldritch/src/runtime/mod.rs +++ b/implants/lib/eldritch/src/runtime/mod.rs @@ -94,7 +94,7 @@ mod tests { parameters: HashMap::new(), file_names: Vec::new(), }, - want_text: format!("{}\n", r#"["append", "compress", "copy", "exists", "find", "follow", "is_dir", "is_file", "list", "mkdir", "moveto", "parent_dir", "read", "remove", "replace", "replace_all", "temp_file", "template", "timestomp", "write"]"#), + want_text: format!("{}\n", r#"["append", "compress", "copy", "exists", "find", "follow", "is_dir", "is_file", "list", "mkdir", "moveto", "parent_dir", "read", "read_binary", "remove", "replace", "replace_all", "temp_file", "template", "timestomp", "write"]"#), want_error: None, }, process_bindings: TestCase {