From 873033754809045ee2a0d124fa0920bec6d036dc Mon Sep 17 00:00:00 2001 From: Joe Abbate <40615740+jabbate19@users.noreply.github.com> Date: Tue, 6 Feb 2024 17:48:00 -0500 Subject: [PATCH] [feature] Eldritch file.follow (#546) * Implement file.follow * Fix follow to only do one file * Clean up follow * Update contribution docs for current practice --- docs/_docs/dev-guide/eldritch.md | 27 ++++++------- docs/_docs/user-guide/eldritch.md | 11 ++++++ implants/Cargo.toml | 1 + implants/lib/eldritch/Cargo.toml | 1 + implants/lib/eldritch/src/file.rs | 7 ++++ implants/lib/eldritch/src/file/follow_impl.rs | 38 +++++++++++++++++++ implants/lib/eldritch/src/runtime.rs | 2 +- 7 files changed, 70 insertions(+), 17 deletions(-) create mode 100644 implants/lib/eldritch/src/file/follow_impl.rs diff --git a/docs/_docs/dev-guide/eldritch.md b/docs/_docs/dev-guide/eldritch.md index 1cf4d8f2..c93bde8a 100644 --- a/docs/_docs/dev-guide/eldritch.md +++ b/docs/_docs/dev-guide/eldritch.md @@ -131,25 +131,20 @@ mod tests { } ``` -### `eldritch/lib.rs` tests +### `eldritch/runtime.rs` tests -Lastly you'll need to add your function to the `eldritch/lib.rs` integration test. +Lastly you'll need to add your function to the `eldritch/runtime.rs` integration test. Add your function to it's respective parent binding in alphabetical order. ```rust - #[test] - fn test_library_bindings() { - let globals = get_eldritch().unwrap(); - let mut a = Assert::new(); - a.globals(globals); - a.all_true( - r#" -dir(file) == ["append", "compress", "copy", "download", "exists", "hash", "is_dir", "is_file", "mkdir", "read", "remove", "rename", "replace", "replace_all", "template", "timestomp", "write"] -dir(process) == ["kill", "list", "name"] -dir(sys) == ["dll_inject", "exec", "is_linux", "is_macos", "is_windows", "shell"] -dir(pivot) == ["arp_scan", "bind_proxy", "ncat", "port_forward", "port_scan", "smb_exec", "ssh_exec", "ssh_password_spray"] -dir(assets) == ["copy","list"] -"#, - ); + // Binding for the "file" functions + file_bindings: TestCase { + tome: Tome { + eldritch: String::from("print(dir(file))"), + parameters: HashMap::new(), + file_names: Vec::new(), + }, + want_output: String::from(r#"["append", "compress", "copy", "download", "exists", "find", "follow", "is_dir", "is_file", "list", "mkdir", "moveto", "read", "remove", "replace", "replace_all", "template", "timestomp", "write"]"#), + want_error: None, } ``` diff --git a/docs/_docs/user-guide/eldritch.md b/docs/_docs/user-guide/eldritch.md index e489f809..457b981b 100644 --- a/docs/_docs/user-guide/eldritch.md +++ b/docs/_docs/user-guide/eldritch.md @@ -266,6 +266,17 @@ The file.download method downloads a file at the URI specified in `uri` t The file.exists method checks if a file or directory exists at the path specified. +### file.follow + +`file.follow(path: str, fn: function(str)) -> None` + +The file.follow method will call `fn(line)` for any new `line` that is added to the file (such as from `bash_history` and other logs). + +```python +# Print every line added to bob's bash history +file.follow('/home/bob/.bash_history', print) +``` + ### file.is_dir `file.is_dir(path: str) -> bool` diff --git a/implants/Cargo.toml b/implants/Cargo.toml index e8d293ed..c00df43b 100644 --- a/implants/Cargo.toml +++ b/implants/Cargo.toml @@ -32,6 +32,7 @@ md5 = "0.7.0" netstat2 = "0.9.1" network-interface = "1.0.1" nix = "0.26.1" +notify = "6.1.1" object = "0.31.1" openssl = "0.10.55" pnet = "0.34.0" diff --git a/implants/lib/eldritch/Cargo.toml b/implants/lib/eldritch/Cargo.toml index c6a6845f..3ee30108 100644 --- a/implants/lib/eldritch/Cargo.toml +++ b/implants/lib/eldritch/Cargo.toml @@ -27,6 +27,7 @@ log = { workspace = true } md5 = { workspace = true } netstat2 = { workspace = true } nix = { workspace = true } +notify = { workspace = true } object = { workspace = true } openssl = { workspace = true, features = ["vendored"] } prost = { workspace = true} diff --git a/implants/lib/eldritch/src/file.rs b/implants/lib/eldritch/src/file.rs index 718a18ed..7a2a9ce6 100644 --- a/implants/lib/eldritch/src/file.rs +++ b/implants/lib/eldritch/src/file.rs @@ -4,6 +4,7 @@ mod copy_impl; mod download_impl; mod exists_impl; mod find_impl; +mod follow_impl; mod is_dir_impl; mod is_file_impl; mod list_impl; @@ -23,6 +24,7 @@ use derive_more::Display; use serde::{Serialize, Serializer}; use starlark::collections::SmallMap; use starlark::environment::{Methods, MethodsBuilder, MethodsStatic}; +use starlark::eval::Evaluator; use starlark::values::dict::Dict; use starlark::values::none::NoneType; use starlark::values::{ @@ -181,4 +183,9 @@ fn methods(builder: &mut MethodsBuilder) { if false { println!("Ignore unused this var. _this isn't allowed by starlark. {:?}", this); } find_impl::find(path, name, file_type, permissions, modified_time, create_time) } + fn follow<'v>(this: FileLibrary, path: String, f: Value<'v>, eval: &mut Evaluator<'v, '_>) -> anyhow::Result { + if false { println!("Ignore unused this var. _this isn't allowed by starlark. {:?}", this); } + follow_impl::follow(path, f, eval)?; + Ok(NoneType{}) + } } diff --git a/implants/lib/eldritch/src/file/follow_impl.rs b/implants/lib/eldritch/src/file/follow_impl.rs new file mode 100644 index 00000000..1326af17 --- /dev/null +++ b/implants/lib/eldritch/src/file/follow_impl.rs @@ -0,0 +1,38 @@ +use std::io::{Seek, BufReader, BufRead}; +use notify::{Watcher, RecursiveMode, RecommendedWatcher, Config}; + +use anyhow::{anyhow, Result}; +use starlark::{eval::Evaluator, values::Value}; + +pub fn follow<'v>(path: String, f: Value<'v>, eval: &mut Evaluator<'v, '_>) -> Result<()> { + let starlark_heap = eval.heap(); + // get pos to end of file + let mut file = std::fs::File::open(&path)?; + let mut pos = std::fs::metadata(&path)?.len(); + + // set up watcher + let (tx, rx) = std::sync::mpsc::channel(); + let mut watcher = RecommendedWatcher::new(tx, Config::default())?; + watcher.watch(path.as_ref(), RecursiveMode::NonRecursive)?; + + // watch + for _event in rx.into_iter().flatten() { + // ignore any event that didn't change the pos + if file.metadata()?.len() == pos { + continue; + } + + // read from pos to end of file + file.seek(std::io::SeekFrom::Start(pos))?; + + // update post to end of file + pos = file.metadata()?.len(); + + let reader = BufReader::new(&file); + for line in reader.lines() { + let val = starlark_heap.alloc(line?.to_string()); + eval.eval_function(f, &[val], &[])?; + } + } + Ok(()) +} \ No newline at end of file diff --git a/implants/lib/eldritch/src/runtime.rs b/implants/lib/eldritch/src/runtime.rs index 92a2cd72..e7d3affe 100644 --- a/implants/lib/eldritch/src/runtime.rs +++ b/implants/lib/eldritch/src/runtime.rs @@ -456,7 +456,7 @@ mod tests { parameters: HashMap::new(), file_names: Vec::new(), }, - want_output: String::from(r#"["append", "compress", "copy", "download", "exists", "find", "is_dir", "is_file", "list", "mkdir", "moveto", "read", "remove", "replace", "replace_all", "template", "timestomp", "write"]"#), + want_output: String::from(r#"["append", "compress", "copy", "download", "exists", "find", "follow", "is_dir", "is_file", "list", "mkdir", "moveto", "read", "remove", "replace", "replace_all", "template", "timestomp", "write"]"#), want_error: None, }, process_bindings: TestCase {