diff --git a/docs/_docs/user-guide/eldritch.md b/docs/_docs/user-guide/eldritch.md index a2af62646..bf6ec1860 100644 --- a/docs/_docs/user-guide/eldritch.md +++ b/docs/_docs/user-guide/eldritch.md @@ -548,3 +548,45 @@ The crypto.hash_file method will produce the hash of the given file's con - SHA1 - SHA256 - SHA512 + +### crypto.encode_b64 +`crypto.encode_b64(content: str, encode_type: Optional) -> str` + +The crypto.encode_b64 method encodes the given text using the given base64 encoding method. Valid methods include: + +- STANDARD (default) +- STANDARD_NO_PAD +- URL_SAFE +- URL_SAFE_NO_PAD + +### crypto.decode_b64 +`crypto.decode_b64(content: str, decode_type: Optional) -> str` + +The crypto.decode_b64 method encodes the given text using the given base64 decoding method. Valid methods include: + +- STANDARD (default) +- STANDARD_NO_PAD +- URL_SAFE +- URL_SAFE_NO_PAD + +### crypto.from_json +`crypto.from_json(content: str) -> Value` + +The crypto.from_json method converts JSON text to an object of correct type. + +```python +crypto.from_json("{\"foo\":\"bar\"}") +{ + "foo": "bar" +} +``` + +### crypto.to_json +`crypto.to_json(content: Value) -> str` + +The crypto.to_json method converts given type to JSON text. + +```python +crypto.to_json({"foo": "bar"}) +"{\"foo\":\"bar\"}" +``` diff --git a/implants/Cargo.toml b/implants/Cargo.toml index fff2084f0..58e242fd9 100644 --- a/implants/Cargo.toml +++ b/implants/Cargo.toml @@ -14,6 +14,7 @@ anyhow = "1.0.65" assert_cmd = "2.0.6" async-recursion = "1.0.0" async-trait = "0.1.68" +base64 = "0.21.4" chrono = "0.4.24" clap = "3.2.23" default-net = "0.13.1" diff --git a/implants/lib/eldritch/Cargo.toml b/implants/lib/eldritch/Cargo.toml index 68d532a52..0f956c5f7 100644 --- a/implants/lib/eldritch/Cargo.toml +++ b/implants/lib/eldritch/Cargo.toml @@ -10,6 +10,7 @@ allocative_derive = { workspace = true } anyhow = { workspace = true } async-recursion = { workspace = true } async-trait = { workspace = true } +base64 = { workspace = true } chrono = { workspace = true } derive_more = { workspace = true } eval = { workspace = true } diff --git a/implants/lib/eldritch/src/crypto.rs b/implants/lib/eldritch/src/crypto.rs index 94c5aae37..8141552a3 100644 --- a/implants/lib/eldritch/src/crypto.rs +++ b/implants/lib/eldritch/src/crypto.rs @@ -1,6 +1,10 @@ mod aes_encrypt_file_impl; mod aes_decrypt_file_impl; mod hash_file_impl; +mod encode_b64_impl; +mod decode_b64_impl; +mod from_json_impl; +mod to_json_impl; use allocative::Allocative; use derive_more::Display; @@ -64,4 +68,20 @@ fn methods(builder: &mut MethodsBuilder) { 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/decode_b64_impl.rs b/implants/lib/eldritch/src/crypto/decode_b64_impl.rs new file mode 100644 index 000000000..dbace2a8f --- /dev/null +++ b/implants/lib/eldritch/src/crypto/decode_b64_impl.rs @@ -0,0 +1,51 @@ +use anyhow::{anyhow, Result}; +use base64::{Engine, engine::general_purpose}; + +pub fn decode_b64(content: String, encode_type: Option) -> Result { + let decode_type = match encode_type.unwrap_or("STANDARD".to_string()).as_str() { + "STANDARD" => {general_purpose::STANDARD}, + "STANDARD_NO_PAD" => {general_purpose::STANDARD_NO_PAD}, + "URL_SAFE" => {general_purpose::URL_SAFE}, + "URL_SAFE_NO_PAD" => {general_purpose::URL_SAFE_NO_PAD}, + _ => return Err(anyhow!("Invalid encode type. Valid types are: STANDARD, STANDARD_NO_PAD, URL_SAFE_PAD, URL_SAFE_NO_PAD")) + }; + decode_type.decode(content.as_bytes()).map(|res| String::from_utf8_lossy(&res).to_string()).map_err(|e| anyhow!("Error decoding base64: {:?}", e)) +} + +#[cfg(test)] +mod tests { + #[test] + fn test_decode_b64() -> anyhow::Result<()>{ + let res = super::decode_b64("dGVzdA==".to_string(), Some("STANDARD".to_string()))?; + assert_eq!(res, "test"); + let res = super::decode_b64("dGVzdA".to_string(), Some("STANDARD_NO_PAD".to_string()))?; + assert_eq!(res, "test"); + let res = super::decode_b64("aHR0cHM6Ly9nb29nbGUuY29tLyY=".to_string(), Some("URL_SAFE".to_string()))?; + assert_eq!(res, "https://google.com/&"); + let res = super::decode_b64("aHR0cHM6Ly9nb29nbGUuY29tLyY".to_string(), Some("URL_SAFE_NO_PAD".to_string()))?; + assert_eq!(res, "https://google.com/&"); + Ok(()) + } + + #[test] + fn test_decode_b64_invalid_type() -> anyhow::Result<()>{ + let res = super::decode_b64("test".to_string(), Some("INVALID".to_string())); + assert!(res.is_err()); + Ok(()) + } + + #[test] + fn test_decode_b64_default_type() -> anyhow::Result<()>{ + let res = super::decode_b64("dGVzdA==".to_string(), None)?; + assert_eq!(res, "test"); + Ok(()) + } + + #[test] + fn test_decode_b64_invalid_content() -> anyhow::Result<()>{ + let res = super::decode_b64("///".to_string(), Some("STANDARD".to_string())); + assert!(res.is_err()); + Ok(()) + } + +} diff --git a/implants/lib/eldritch/src/crypto/encode_b64_impl.rs b/implants/lib/eldritch/src/crypto/encode_b64_impl.rs new file mode 100644 index 000000000..b4b974d28 --- /dev/null +++ b/implants/lib/eldritch/src/crypto/encode_b64_impl.rs @@ -0,0 +1,43 @@ +use anyhow::{anyhow, Result}; +use base64::{Engine, engine::general_purpose}; + +pub fn encode_b64(content: String, encode_type: Option) -> Result { + let encode_type = match encode_type.unwrap_or("STANDARD".to_string()).as_str() { + "STANDARD" => {general_purpose::STANDARD}, + "STANDARD_NO_PAD" => {general_purpose::STANDARD_NO_PAD}, + "URL_SAFE" => {general_purpose::URL_SAFE}, + "URL_SAFE_NO_PAD" => {general_purpose::URL_SAFE_NO_PAD}, + _ => return Err(anyhow!("Invalid encode type. Valid types are: STANDARD, STANDARD_NO_PAD, URL_SAFE_PAD, URL_SAFE_NO_PAD")) + }; + Ok(encode_type.encode(content.as_bytes())) +} + +#[cfg(test)] +mod tests { + #[test] + fn test_encode_b64() -> anyhow::Result<()>{ + let res = super::encode_b64("test".to_string(), Some("STANDARD".to_string()))?; + assert_eq!(res, "dGVzdA=="); + let res = super::encode_b64("test".to_string(), Some("STANDARD_NO_PAD".to_string()))?; + assert_eq!(res, "dGVzdA"); + let res = super::encode_b64("https://google.com/&".to_string(), Some("URL_SAFE".to_string()))?; + assert_eq!(res, "aHR0cHM6Ly9nb29nbGUuY29tLyY="); + let res = super::encode_b64("https://google.com/&".to_string(), Some("URL_SAFE_NO_PAD".to_string()))?; + assert_eq!(res, "aHR0cHM6Ly9nb29nbGUuY29tLyY"); + Ok(()) + } + + #[test] + fn test_encode_b64_invalid_type() -> anyhow::Result<()>{ + let res = super::encode_b64("test".to_string(), Some("INVALID".to_string())); + assert!(res.is_err()); + Ok(()) + } + + #[test] + fn test_encode_b64_default_type() -> anyhow::Result<()>{ + let res = super::encode_b64("test".to_string(), None)?; + assert_eq!(res, "dGVzdA=="); + Ok(()) + } +} \ No newline at end of file diff --git a/implants/lib/eldritch/src/crypto/from_json_impl.rs b/implants/lib/eldritch/src/crypto/from_json_impl.rs new file mode 100644 index 000000000..fd38b9d65 --- /dev/null +++ b/implants/lib/eldritch/src/crypto/from_json_impl.rs @@ -0,0 +1,40 @@ +use anyhow::{anyhow, Result}; +use starlark::values::{Heap, Value}; + +pub fn from_json(starlark_heap: &Heap, json: String) -> Result { + let json_data: serde_json::Value = serde_json::from_str(&json).map_err(|e| anyhow!("Error parsing json: {:?}", e))?; + + Ok(starlark_heap.alloc(json_data.clone())) +} + +#[cfg(test)] +mod tests { + use serde_json::json; + use starlark::values::{Heap, Value}; + + #[test] + fn test_from_json_object() -> anyhow::Result<()>{ + let test_heap = Heap::new(); + let res = super::from_json(&test_heap, r#"{"test": "test"}"#.to_string())?; + let res_value = test_heap.alloc(json!({"test": "test"})); + assert_eq!(res, res_value); + Ok(()) + } + + #[test] + fn test_from_json_list() -> anyhow::Result<()>{ + let test_heap = Heap::new(); + let res = super::from_json(&test_heap, r#"[1, "foo", false, null]"#.to_string())?; + let res_value = test_heap.alloc(json!([1, "foo", false, null])); + assert_eq!(res, res_value); + Ok(()) + } + + #[test] + fn test_from_json_invalid() -> anyhow::Result<()>{ + let test_heap = Heap::new(); + let res = super::from_json(&test_heap, r#"{"test":"#.to_string()); + assert!(res.is_err()); + Ok(()) + } +} \ No newline at end of file diff --git a/implants/lib/eldritch/src/crypto/to_json_impl.rs b/implants/lib/eldritch/src/crypto/to_json_impl.rs new file mode 100644 index 000000000..d884c8b91 --- /dev/null +++ b/implants/lib/eldritch/src/crypto/to_json_impl.rs @@ -0,0 +1,40 @@ +use anyhow::Result; +use starlark::values::Value; + +pub fn to_json(json: Value) -> Result { + json.to_json() +} + +#[cfg(test)] +mod tests { + use starlark::{values::{dict::Dict, Heap, Value}, const_frozen_string, collections::SmallMap}; + use anyhow::Result; + + #[test] + fn to_json_object() -> Result<()> { + let test_heap = Heap::new(); + let res = SmallMap::new(); + let mut dict_res = Dict::new(res); + dict_res.insert_hashed( + const_frozen_string!("test").to_value().get_hashed()?, + test_heap.alloc_str("test").to_value(), + ); + let res = super::to_json(test_heap.alloc(dict_res))?; + assert_eq!(res, r#"{"test":"test"}"#); + Ok(()) + } + + #[test] + fn to_json_list() -> Result<()> { + let test_heap = Heap::new(); + let mut vec_val: Vec = Vec::new(); + vec_val.push(test_heap.alloc(1)); + vec_val.push(test_heap.alloc("foo")); + vec_val.push(test_heap.alloc(false)); + vec_val.push(Value::new_none()); + let res = test_heap.alloc(vec_val); + let res = super::to_json(res)?; + assert_eq!(res, r#"[1,"foo",false,null]"#); + Ok(()) + } +} \ No newline at end of file diff --git a/implants/lib/eldritch/src/lib.rs b/implants/lib/eldritch/src/lib.rs index 9fe8dab21..d73a88ce8 100644 --- a/implants/lib/eldritch/src/lib.rs +++ b/implants/lib/eldritch/src/lib.rs @@ -193,7 +193,7 @@ dir(process) == ["kill", "list", "name"] dir(sys) == ["dll_inject", "exec", "get_env", "get_ip", "get_os", "get_pid", "get_user", "is_linux", "is_macos", "is_windows", "shell"] dir(pivot) == ["arp_scan", "bind_proxy", "ncat", "port_forward", "port_scan", "smb_exec", "ssh_copy", "ssh_exec", "ssh_password_spray"] dir(assets) == ["copy","list","read","read_binary"] -dir(crypto) == ["aes_decrypt_file", "aes_encrypt_file", "hash_file"] +dir(crypto) == ["aes_decrypt_file", "aes_encrypt_file", "decode_b64", "encode_b64", "from_json", "hash_file", "to_json"] "#, ); }