From 8c7abe8cf06500d41441b0685a6a8d8754474b65 Mon Sep 17 00:00:00 2001 From: Joe Abbate Date: Sun, 4 May 2025 20:19:46 +0000 Subject: [PATCH 1/3] Add file.read_binary --- docs/_docs/user-guide/eldritch.md | 13 ++ implants/lib/eldritch/src/file/mod.rs | 6 + .../lib/eldritch/src/file/read_binary_impl.rs | 111 ++++++++++++++++++ 3 files changed, 130 insertions(+) create mode 100644 implants/lib/eldritch/src/file/read_binary_impl.rs diff --git a/docs/_docs/user-guide/eldritch.md b/docs/_docs/user-guide/eldritch.md index cdba4c4cb..901237088 100644 --- a/docs/_docs/user-guide/eldritch.md +++ b/docs/_docs/user-guide/eldritch.md @@ -378,6 +378,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..6fa12b300 100644 --- a/implants/lib/eldritch/src/file/mod.rs +++ b/implants/lib/eldritch/src/file/mod.rs @@ -11,6 +11,7 @@ mod mkdir_impl; mod moveto_impl; mod parent_dir_impl; mod read_impl; +mod read_binary_impl; mod remove_impl; mod replace_all_impl; mod replace_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..e74dafdb2 --- /dev/null +++ b/implants/lib/eldritch/src/file/read_binary_impl.rs @@ -0,0 +1,111 @@ +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(()) + } +} From cf1aeccd7040722054d5d10222e2b5d825e28ab5 Mon Sep 17 00:00:00 2001 From: Joe Abbate Date: Sun, 4 May 2025 20:24:24 +0000 Subject: [PATCH 2/3] Fmt --- implants/lib/eldritch/src/file/mod.rs | 2 +- implants/lib/eldritch/src/file/read_binary_impl.rs | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/implants/lib/eldritch/src/file/mod.rs b/implants/lib/eldritch/src/file/mod.rs index 6fa12b300..38af506c5 100644 --- a/implants/lib/eldritch/src/file/mod.rs +++ b/implants/lib/eldritch/src/file/mod.rs @@ -10,8 +10,8 @@ mod list_impl; mod mkdir_impl; mod moveto_impl; mod parent_dir_impl; -mod read_impl; mod read_binary_impl; +mod read_impl; mod remove_impl; mod replace_all_impl; mod replace_impl; diff --git a/implants/lib/eldritch/src/file/read_binary_impl.rs b/implants/lib/eldritch/src/file/read_binary_impl.rs index e74dafdb2..ac18870d9 100644 --- a/implants/lib/eldritch/src/file/read_binary_impl.rs +++ b/implants/lib/eldritch/src/file/read_binary_impl.rs @@ -34,7 +34,9 @@ mod tests { 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]; + 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<()> { @@ -105,7 +107,10 @@ mod tests { 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])); + assert_eq!( + res, + Vec::from([0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x0a, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x0a]) + ); Ok(()) } } From b4470de7f1f6c50bd8ab5693e6dfcc691fa6d833 Mon Sep 17 00:00:00 2001 From: Joe Abbate Date: Sun, 4 May 2025 20:30:20 +0000 Subject: [PATCH 3/3] Fix Runtime Test --- implants/lib/eldritch/src/runtime/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/implants/lib/eldritch/src/runtime/mod.rs b/implants/lib/eldritch/src/runtime/mod.rs index d3f97d28d..1b16c916f 100644 --- a/implants/lib/eldritch/src/runtime/mod.rs +++ b/implants/lib/eldritch/src/runtime/mod.rs @@ -89,7 +89,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 {