|
|
|
@ -1,9 +1,8 @@ |
|
|
|
|
// This module is aiming to store obfuscated payloads, get them by name,
|
|
|
|
|
// rename, update, delete or prepare to execute via memfd_create (unix)
|
|
|
|
|
|
|
|
|
|
use once_cell::sync::Lazy; |
|
|
|
|
use parking_lot::RwLock; |
|
|
|
|
use std::collections::HashMap; |
|
|
|
|
use anyhow::{Context, Result}; |
|
|
|
|
use serde::{Deserialize, Serialize}; |
|
|
|
|
use std::env::temp_dir; |
|
|
|
|
use std::ffi::{CString, OsString}; |
|
|
|
|
use std::fs::{self, File}; |
|
|
|
@ -11,75 +10,84 @@ use std::path::{Path, PathBuf}; |
|
|
|
|
use uuid::Uuid; |
|
|
|
|
|
|
|
|
|
mod error; |
|
|
|
|
mod index; |
|
|
|
|
pub use error::Error; |
|
|
|
|
|
|
|
|
|
// INDEX format: given_name -> payload_meta
|
|
|
|
|
static INDEX: Lazy<RwLock<HashMap<String, FileMeta>>> = Lazy::new(|| RwLock::new(HashMap::new())); |
|
|
|
|
const OBFUSCATE: bool = cfg!(feature = "agent"); |
|
|
|
|
|
|
|
|
|
struct FileMeta { |
|
|
|
|
path: PathBuf, |
|
|
|
|
obfuscated: bool, |
|
|
|
|
#[derive(Clone, Deserialize, Serialize)] |
|
|
|
|
pub struct FileMeta { |
|
|
|
|
extension: Option<OsString>, |
|
|
|
|
external: bool, |
|
|
|
|
hash: Vec<u8>, |
|
|
|
|
pub path: PathBuf, |
|
|
|
|
pub size: u64, |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Remove deleted files from index
|
|
|
|
|
pub fn sync_index() { |
|
|
|
|
let mut index = INDEX.write(); |
|
|
|
|
impl FileMeta { |
|
|
|
|
pub fn new( |
|
|
|
|
full_path: impl Into<PathBuf>, |
|
|
|
|
hash: Vec<u8>, |
|
|
|
|
external: bool, |
|
|
|
|
) -> Result<Self, Error> { |
|
|
|
|
let full_path: PathBuf = full_path.into(); |
|
|
|
|
let extension = full_path.extension().map(ToOwned::to_owned); |
|
|
|
|
let size = fs::metadata(&full_path)?.len(); |
|
|
|
|
|
|
|
|
|
let files_to_delete: Vec<String> = index |
|
|
|
|
.iter() |
|
|
|
|
.filter_map(|(name, meta)| { |
|
|
|
|
if meta.path.exists() { |
|
|
|
|
None |
|
|
|
|
} else { |
|
|
|
|
Some(name.to_string()) |
|
|
|
|
} |
|
|
|
|
Ok(FileMeta { |
|
|
|
|
path: full_path, |
|
|
|
|
extension, |
|
|
|
|
external, |
|
|
|
|
hash, |
|
|
|
|
size, |
|
|
|
|
}) |
|
|
|
|
.collect(); |
|
|
|
|
|
|
|
|
|
files_to_delete.into_iter().for_each(|f| { |
|
|
|
|
index.remove(&f); |
|
|
|
|
}); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
pub fn in_index(name: impl AsRef<str>) -> bool { |
|
|
|
|
sync_index(); |
|
|
|
|
|
|
|
|
|
INDEX.read().get(name.as_ref()).is_some() |
|
|
|
|
read_meta(name).is_ok() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
pub fn read(name: impl AsRef<str>) -> Result<Vec<u8>, Error> { |
|
|
|
|
sync_index(); |
|
|
|
|
|
|
|
|
|
let name = name.as_ref(); |
|
|
|
|
let index = INDEX.read(); |
|
|
|
|
let meta = index.get(name).ok_or_else(|| Error::not_found(name))?; |
|
|
|
|
#[inline] |
|
|
|
|
pub fn read_meta(name: impl AsRef<str>) -> Result<FileMeta> { |
|
|
|
|
index::get(&name) |
|
|
|
|
.ok_or_else(|| Error::not_found(name.as_ref())) |
|
|
|
|
.context("meta") |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fs::read(&meta.path).map_err(|e| Error::new(e, name)) |
|
|
|
|
pub fn read(name: impl AsRef<str>) -> Result<Vec<u8>> { |
|
|
|
|
let meta = read_meta(&name).context("read_meta")?; |
|
|
|
|
fs::read(&meta.path) |
|
|
|
|
.map_err(|e| Error::new(e, name.as_ref())) |
|
|
|
|
.context("read") |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
pub fn create(data: impl AsRef<[u8]>) -> Result<String, Error> { |
|
|
|
|
pub fn create_anonymous(data: impl AsRef<[u8]>) -> Result<String> { |
|
|
|
|
if let Some((name, _)) = index::get_by_hash(hash_data(&data)) { |
|
|
|
|
return Ok(name); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
let name = Uuid::new_v4().simple().to_string(); |
|
|
|
|
|
|
|
|
|
put(&name, data)?; |
|
|
|
|
put(&name, data).context("cr_anon")?; |
|
|
|
|
|
|
|
|
|
Ok(name) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Create new file and add to index
|
|
|
|
|
pub fn put(name: impl AsRef<str>, data: impl AsRef<[u8]>) -> Result<(), Error> { |
|
|
|
|
sync_index(); |
|
|
|
|
|
|
|
|
|
pub fn put(name: impl AsRef<str>, data: impl AsRef<[u8]>) -> Result<()> { |
|
|
|
|
let name = name.as_ref(); |
|
|
|
|
let obfuscate = !cfg!(feature = "server") && !cfg!(feature = "panel"); |
|
|
|
|
let data_hash = hash_data(&data); |
|
|
|
|
|
|
|
|
|
if in_index(&name) { |
|
|
|
|
return Err(Error::already_exists(&name)); |
|
|
|
|
return Err(Error::already_exists(&name)).context("put_exists"); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
let path = match index::get_by_hash(&data_hash) { |
|
|
|
|
Some((_, meta)) => meta.path, |
|
|
|
|
None => { |
|
|
|
|
let path = { |
|
|
|
|
let exec_name = if obfuscate { |
|
|
|
|
let exec_name = if OBFUSCATE { |
|
|
|
|
PathBuf::from(Uuid::new_v4().simple().to_string()) |
|
|
|
|
} else { |
|
|
|
|
PathBuf::from(name) |
|
|
|
@ -90,37 +98,32 @@ pub fn put(name: impl AsRef<str>, data: impl AsRef<[u8]>) -> Result<(), Error> { |
|
|
|
|
path |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
let extension = path.file_stem().map(ToOwned::to_owned); |
|
|
|
|
|
|
|
|
|
fs::write(&path, data).map_err(|e| Error::new(e, name))?; |
|
|
|
|
|
|
|
|
|
INDEX.write().insert( |
|
|
|
|
name.to_string(), |
|
|
|
|
FileMeta { |
|
|
|
|
path, |
|
|
|
|
obfuscated: obfuscate, |
|
|
|
|
extension, |
|
|
|
|
external: false, |
|
|
|
|
}, |
|
|
|
|
fs::write(&path, &data) |
|
|
|
|
.map_err(|e| Error::new(e, name)) |
|
|
|
|
.context("put_write")?; |
|
|
|
|
path |
|
|
|
|
} |
|
|
|
|
}; |
|
|
|
|
index::insert( |
|
|
|
|
name, |
|
|
|
|
FileMeta::new(path, data_hash, false).context("put_insert")?, |
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
Ok(()) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
pub fn remove(name: impl AsRef<str>) -> Result<(), Error> { |
|
|
|
|
sync_index(); |
|
|
|
|
|
|
|
|
|
pub fn remove(name: impl AsRef<str>) -> Result<()> { |
|
|
|
|
let name = name.as_ref(); |
|
|
|
|
|
|
|
|
|
match INDEX.write().remove(name) { |
|
|
|
|
Some(value) => fs::remove_file(value.path).map_err(|e| Error::new(e, name)), |
|
|
|
|
None => Ok(()), |
|
|
|
|
match index::remove(name) { |
|
|
|
|
Some(value) if !value.external => fs::remove_file(value.path) |
|
|
|
|
.map_err(|e| Error::new(e, name)) |
|
|
|
|
.context("remove"), |
|
|
|
|
_ => Ok(()), |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
pub fn rename(old_name: impl AsRef<str>, new_name: impl AsRef<str>) -> Result<(), Error> { |
|
|
|
|
sync_index(); |
|
|
|
|
|
|
|
|
|
// todo: don't rename external files
|
|
|
|
|
pub fn rename(old_name: impl AsRef<str>, new_name: impl AsRef<str>) -> Result<()> { |
|
|
|
|
let old_name = old_name.as_ref(); |
|
|
|
|
let new_name = new_name.as_ref(); |
|
|
|
|
|
|
|
|
@ -129,67 +132,63 @@ pub fn rename(old_name: impl AsRef<str>, new_name: impl AsRef<str>) -> Result<() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if !in_index(old_name) { |
|
|
|
|
return Err(Error::not_found(old_name)); |
|
|
|
|
return Err(Error::not_found(old_name)).context("rename"); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if in_index(new_name) { |
|
|
|
|
return Err(Error::already_exists(new_name)); |
|
|
|
|
return Err(Error::already_exists(new_name)).context("rename"); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
let mut value = INDEX.write().remove(old_name).unwrap(); |
|
|
|
|
let mut value = index::remove(old_name).unwrap(); |
|
|
|
|
|
|
|
|
|
if !value.obfuscated { |
|
|
|
|
if !OBFUSCATE { |
|
|
|
|
let old_path = value.path.clone(); |
|
|
|
|
|
|
|
|
|
value.path.pop(); |
|
|
|
|
value.path.push(new_name); |
|
|
|
|
|
|
|
|
|
fs::rename(old_path, &value.path).map_err(|e| Error::new(e, &value.path))?; |
|
|
|
|
fs::rename(old_path, &value.path) |
|
|
|
|
.map_err(|e| Error::new(e, &value.path)) |
|
|
|
|
.context("rename")?; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
INDEX.write().insert(new_name.to_string(), value); |
|
|
|
|
index::insert(new_name, value); |
|
|
|
|
|
|
|
|
|
Ok(()) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
pub fn update_payload_data(name: impl AsRef<str>, data: impl AsRef<[u8]>) -> Result<(), Error> { |
|
|
|
|
sync_index(); |
|
|
|
|
|
|
|
|
|
pub fn update_payload_data(name: impl AsRef<str>, data: impl AsRef<[u8]>) -> Result<()> { |
|
|
|
|
let name = name.as_ref(); |
|
|
|
|
let external = INDEX.read().get(name).map(|v| v.external).unwrap_or(false); |
|
|
|
|
let external = index::get(name).map(|v| v.external).unwrap_or(false); |
|
|
|
|
|
|
|
|
|
if external { |
|
|
|
|
INDEX.write().remove(name); |
|
|
|
|
index::remove(name); |
|
|
|
|
} else if in_index(&name) { |
|
|
|
|
remove(&name)?; |
|
|
|
|
remove(&name).context("upd")?; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
put(name, data) |
|
|
|
|
put(name, data).context("upd") |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Add an existing file to index
|
|
|
|
|
pub fn put_external(path: impl AsRef<Path>) -> Result<(), Error> { |
|
|
|
|
sync_index(); |
|
|
|
|
|
|
|
|
|
pub fn put_external(path: impl AsRef<Path>) -> Result<()> { |
|
|
|
|
let path = path.as_ref(); |
|
|
|
|
let path_str = path.as_os_str().to_string_lossy().to_string(); |
|
|
|
|
|
|
|
|
|
if !path.exists() || path.is_dir() { |
|
|
|
|
return Err(Error::not_found(path)); |
|
|
|
|
if in_index(&path_str) { |
|
|
|
|
return Ok(()); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if in_index(&path_str) { |
|
|
|
|
return Err(Error::already_exists(&path)); |
|
|
|
|
if !path.exists() || path.is_dir() { |
|
|
|
|
return Err(Error::not_found(path)).context("ext1"); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
INDEX.write().insert( |
|
|
|
|
let file_data = fs::read(&path_str).unwrap(); |
|
|
|
|
let data_hash = hash_data(&file_data); |
|
|
|
|
|
|
|
|
|
index::insert( |
|
|
|
|
path_str, |
|
|
|
|
FileMeta { |
|
|
|
|
path: path.to_owned(), |
|
|
|
|
obfuscated: false, |
|
|
|
|
extension: path.file_stem().map(ToOwned::to_owned), |
|
|
|
|
external: true, |
|
|
|
|
}, |
|
|
|
|
FileMeta::new(path, data_hash, true).context("ext2")?, |
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
Ok(()) |
|
|
|
@ -197,7 +196,7 @@ pub fn put_external(path: impl AsRef<Path>) -> Result<(), Error> { |
|
|
|
|
|
|
|
|
|
/// Prepare executable file: unpack, decipher if needed and send under memfd
|
|
|
|
|
#[cfg(unix)] |
|
|
|
|
pub fn prepare_executable(name: impl AsRef<str>) -> Result<(File, String), Error> { |
|
|
|
|
pub fn prepare_executable(name: impl AsRef<str>) -> Result<(File, String)> { |
|
|
|
|
use libc::getpid; |
|
|
|
|
use nix::sys::memfd::*; |
|
|
|
|
use std::io::{Read, Write}; |
|
|
|
@ -206,12 +205,8 @@ pub fn prepare_executable(name: impl AsRef<str>) -> Result<(File, String), Error |
|
|
|
|
const FAKE_EXEC_NAME: &str = "/usr/sbin/lvmetad"; |
|
|
|
|
const BUFFER_LEN: usize = 4096; |
|
|
|
|
|
|
|
|
|
sync_index(); |
|
|
|
|
|
|
|
|
|
let mut buffer: [u8; BUFFER_LEN] = [0; BUFFER_LEN]; |
|
|
|
|
let name = name.as_ref(); |
|
|
|
|
let index = INDEX.read(); |
|
|
|
|
let payload_meta = index.get(name).ok_or_else(|| Error::not_found(name))?; |
|
|
|
|
let payload_meta = read_meta(name).context("prep")?; |
|
|
|
|
|
|
|
|
|
let fd = memfd_create( |
|
|
|
|
CString::new(FAKE_EXEC_NAME).unwrap().as_c_str(), |
|
|
|
@ -235,15 +230,16 @@ pub fn prepare_executable(name: impl AsRef<str>) -> Result<(File, String), Error |
|
|
|
|
let payload_path = format!("/proc/{}/fd/{}", unsafe { getpid() }, fd); |
|
|
|
|
Ok((payload_dest, payload_path)) |
|
|
|
|
} |
|
|
|
|
Err(e) => Err(Error::new(e, FAKE_EXEC_NAME)), |
|
|
|
|
Err(e) => Err(Error::new(e, FAKE_EXEC_NAME)).context("prep"), |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
#[cfg(windows)] |
|
|
|
|
pub fn prepare_executable(name: impl AsRef<str>) -> Result<(File, String), Error> { |
|
|
|
|
pub fn prepare_executable(name: impl AsRef<str>) -> Result<(File, String)> { |
|
|
|
|
todo!() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* |
|
|
|
|
pub fn cleanup() { |
|
|
|
|
let index = INDEX.read(); |
|
|
|
|
|
|
|
|
@ -251,3 +247,12 @@ pub fn cleanup() { |
|
|
|
|
fs::remove_file(&f.path).ok(); |
|
|
|
|
}); |
|
|
|
|
} |
|
|
|
|
*/ |
|
|
|
|
|
|
|
|
|
fn hash_data(data: impl AsRef<[u8]>) -> Vec<u8> { |
|
|
|
|
use sha3::{Digest, Sha3_256}; |
|
|
|
|
|
|
|
|
|
let mut hasher = Sha3_256::new(); |
|
|
|
|
hasher.update(data); |
|
|
|
|
hasher.finalize().to_vec() |
|
|
|
|
} |
|
|
|
|