Adversarial C2 Protocol Implemented in Zig
0
fork

Configure Feed

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

at dev 182 lines 6.2 kB view raw
1// Copyright 2026 Robby Zambito 2// 3// This file is part of zaprus. 4// 5// Zaprus is free software: you can redistribute it and/or modify it under the 6// terms of the GNU General Public License as published by the Free Software 7// Foundation, either version 3 of the License, or (at your option) any later 8// version. 9// 10// Zaprus is distributed in the hope that it will be useful, but WITHOUT ANY 11// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 12// A PARTICULAR PURPOSE. See the GNU General Public License for more details. 13// 14// You should have received a copy of the GNU General Public License along with 15// Zaprus. If not, see <https://www.gnu.org/licenses/>. 16 17//! A client is used to handle interactions with the network. 18 19const base64_enc = std.base64.standard.Encoder; 20const base64_dec = std.base64.standard.Decoder; 21 22const Client = @This(); 23 24const max_message_size = 2048; 25 26pub const max_payload_len = RawSocket.max_payload_len; 27 28socket: RawSocket, 29 30pub fn init() !Client { 31 const socket: RawSocket = try .init(); 32 return .{ 33 .socket = socket, 34 }; 35} 36 37pub fn deinit(self: *Client) void { 38 self.socket.deinit(); 39 self.* = undefined; 40} 41 42/// Sends a fire and forget message over the network. 43/// This function asserts that `payload` fits within a single packet. 44pub fn sendRelay(self: *Client, io: Io, payload: []const u8, dest: [4]u8) !void { 45 const io_source: std.Random.IoSource = .{ .io = io }; 46 const rand = io_source.interface(); 47 48 var headers: EthIpUdp = .{ 49 .src_mac = .fromBytes(self.socket.mac), 50 .ip = .{ 51 .id = rand.int(u16), 52 .src_addr = .fromBytes(.{ 0, 0, 0, 0 }), //rand.int(u32), 53 .dst_addr = .fromBytes(.{ 255, 255, 255, 255 }), 54 .len = undefined, 55 }, 56 .udp = .{ 57 .src_port = rand.intRangeAtMost(u16, 1025, std.math.maxInt(u16)), 58 .dst_port = 8888, 59 .len = undefined, 60 }, 61 }; 62 63 const relay: SaprusMessage = .{ 64 .relay = .{ 65 .dest = .fromBytes(&dest), 66 .payload = payload, 67 }, 68 }; 69 70 var relay_buf: [max_message_size - (@bitSizeOf(EthIpUdp) / 8)]u8 = undefined; 71 const relay_bytes = relay.toBytes(&relay_buf); 72 headers.setPayloadLen(relay_bytes.len); 73 74 var msg_buf: [max_message_size]u8 = undefined; 75 var msg_w: Writer = .fixed(&msg_buf); 76 msg_w.writeAll(&headers.toBytes()) catch unreachable; 77 msg_w.writeAll(relay_bytes) catch unreachable; 78 const full_msg = msg_w.buffered(); 79 80 try self.socket.send(full_msg); 81} 82 83/// Attempts to establish a new connection with the sentinel. 84pub fn connect(self: Client, io: Io, payload: []const u8) (error{ BpfAttachFailed, Timeout } || SaprusMessage.ParseError)!SaprusConnection { 85 const io_source: std.Random.IoSource = .{ .io = io }; 86 const rand = io_source.interface(); 87 88 var headers: EthIpUdp = .{ 89 .src_mac = .fromBytes(self.socket.mac), 90 .ip = .{ 91 .id = rand.int(u16), 92 .src_addr = .fromBytes(.{ 0, 0, 0, 0 }), //rand.int(u32), 93 .dst_addr = .fromBytes(.{ 255, 255, 255, 255 }), 94 .len = undefined, 95 }, 96 .udp = .{ 97 .src_port = rand.intRangeAtMost(u16, 1025, std.math.maxInt(u16)), 98 .dst_port = 8888, 99 .len = undefined, 100 }, 101 }; 102 103 // udp dest port should not be 8888 after first 104 const udp_dest_port = rand.intRangeAtMost(u16, 9000, std.math.maxInt(u16)); 105 var connection: SaprusMessage = .{ 106 .connection = .{ 107 .src = rand.intRangeAtMost(u16, 1025, std.math.maxInt(u16)), 108 .dest = rand.intRangeAtMost(u16, 1025, std.math.maxInt(u16)), // Ignored, but good noise 109 .seq = undefined, 110 .id = undefined, 111 .payload = payload, 112 }, 113 }; 114 115 log.debug("Setting bpf filter to port {}", .{connection.connection.src}); 116 self.socket.attachSaprusPortFilter(null, connection.connection.src) catch |err| { 117 log.err("Failed to set port filter: {t}", .{err}); 118 return err; 119 }; 120 log.debug("bpf set", .{}); 121 122 var connection_buf: [2048]u8 = undefined; 123 var connection_bytes = connection.toBytes(&connection_buf); 124 headers.setPayloadLen(connection_bytes.len); 125 126 log.debug("Building full message", .{}); 127 var msg_buf: [2048]u8 = undefined; 128 var msg_w: Writer = .fixed(&msg_buf); 129 msg_w.writeAll(&headers.toBytes()) catch unreachable; 130 msg_w.writeAll(connection_bytes) catch unreachable; 131 var full_msg = msg_w.buffered(); 132 log.debug("Built full message. Sending message", .{}); 133 134 try self.socket.send(full_msg); 135 var res_buf: [4096]u8 = undefined; 136 137 log.debug("Awaiting handshake response", .{}); 138 // Ignore response from sentinel, just accept that we got one. 139 const full_handshake_res = try self.socket.receive(&res_buf); 140 const handshake_res = saprusParse(full_handshake_res[42..]) catch |err| { 141 log.err("Parse error: {t}", .{err}); 142 return err; 143 }; 144 self.socket.attachSaprusPortFilter(handshake_res.connection.src, handshake_res.connection.dest) catch |err| { 145 log.err("Failed to set port filter: {t}", .{err}); 146 return err; 147 }; 148 connection.connection.dest = handshake_res.connection.src; 149 connection_bytes = connection.toBytes(&connection_buf); 150 151 headers.udp.dst_port = udp_dest_port; 152 headers.ip.id = rand.int(u16); 153 headers.setPayloadLen(connection_bytes.len); 154 155 log.debug("Building final handshake message", .{}); 156 157 msg_w.end = 0; 158 159 msg_w.writeAll(&headers.toBytes()) catch unreachable; 160 msg_w.writeAll(connection_bytes) catch unreachable; 161 full_msg = msg_w.buffered(); 162 163 try self.socket.send(full_msg); 164 165 return .{ 166 .socket = self.socket, 167 .headers = headers, 168 .connection = connection, 169 }; 170} 171 172const RawSocket = @import("./RawSocket.zig"); 173 174const SaprusMessage = @import("message.zig").Message; 175const saprusParse = SaprusMessage.parse; 176const SaprusConnection = @import("Connection.zig"); 177const EthIpUdp = @import("./EthIpUdp.zig").EthIpUdp; 178 179const std = @import("std"); 180const Io = std.Io; 181const Writer = std.Io.Writer; 182const log = std.log;