Adversarial C2 Protocol Implemented in Zig
0
fork

Configure Feed

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

Arrange bytes for relay

+145 -72
+139 -72
src/main.zig
··· 90 90 std.debug.print("dest: {s}\n", .{flags.dest orelse "<null>"}); 91 91 std.debug.print("connect: {s}\n", .{flags.connect orelse "<null>"}); 92 92 93 + // const rand = blk: { 94 + // const io_source: std.Random.IoSource = .{ .io = init.io }; 95 + // break :blk io_source.interface(); 96 + // }; 97 + 93 98 // const net_interface: std.Io.net.Interface = .{ .index = 1 }; 94 99 // std.debug.print("Interface: {s}\n", .{(try net_interface.name(init.io)).toSlice()}); 100 + const EthIpUdp = packed struct(u336) { // 42 bytes * 8 bits = 336 101 + // --- UDP (Last in memory, defined first for LSB->MSB) --- 102 + checksum: u16 = 0, 103 + udp_len: u16, 104 + dst_port: u16, 105 + src_port: u16, 95 106 96 - const linux_socket = blk: { 97 - const linux_socket = std.os.linux.socket(AF.PACKET, SOCK.RAW, 0); 98 - const errno = std.os.linux.errno(linux_socket); 99 - if (errno != .SUCCESS) { 100 - std.debug.print("Failed to open socket: {t}\n", .{errno}); 101 - return error.Error; // TODO: better error 102 - } 103 - break :blk linux_socket; 104 - }; 105 - const socket_fd = blk: { 106 - const socket_fd = std.os.linux.bind(@intCast(linux_socket), @bitCast(std.os.linux.sockaddr.ll{ 107 - // https://codeberg.org/jeffective/gatorcat/src/commit/1da40c85c2d063368e2e5c130e654cb32b6bff0e/src/module/nic.zig#L137 108 - .protocol = std.mem.nativeToBig(u16, @as(u16, std.os.linux.ETH.P.ALL)), 109 - .ifindex = 1, 110 - .hatype = 0, 111 - .pkttype = 0, 112 - .halen = 0, 113 - .addr = @splat(0), 114 - }), @sizeOf(std.os.linux.sockaddr.ll)); 107 + // --- IP --- 108 + dst_addr: u32, 109 + src_addr: u32, 110 + header_checksum: u16 = 0, 111 + protocol: u8 = 17, // udp 112 + ttl: u8 = 0x40, 115 113 116 - const errno = std.os.linux.errno(socket_fd); 114 + // fragment_offset (13 bits) + flags (3 bits) = 16 bits 115 + // In Big Endian, flags are the high bits of the first byte. 116 + // To have flags appear first in the stream, define them last here. 117 + fragment_offset: u13 = 0, 118 + flags: packed struct(u3) { 119 + reserved: u1 = 0, 120 + dont_fragment: u1 = 0, 121 + more_fragments: u1 = 0, 122 + } = .{}, 117 123 118 - if (errno != .SUCCESS) { 119 - std.debug.print("Failed to create link layer socket: {t}\n", .{errno}); 120 - return error.Error; // TODO: better error 121 - } 122 - break :blk socket_fd; 123 - }; 124 + id: u16, 125 + total_length: u16, 126 + tos: u8 = undefined, 124 127 125 - const EthIpUdp = struct { 126 - // eth 127 - dst_mac: [6]u8 = @splat(0xff), 128 - src_mac: [6]u8, 128 + // ip_version (4 bits) + ihl (4 bits) = 8 bits 129 + // To have version appear first (high nibble), define it last. 130 + ihl: u4 = 5, 131 + ip_version: u4 = 4, 132 + 133 + // --- Ethernet --- 129 134 eth_type: u16 = std.os.linux.ETH.P.IP, 130 - // ip 131 - ip_version: u4 = 4, 135 + src_mac: @Vector(6, u8), 136 + dst_mac: @Vector(6, u8) = @splat(0xff), 137 + 138 + fn toBytes(self: @This()) [336 / 8]u8 { 139 + var res: [336 / 8]u8 = undefined; 140 + var w: Writer = .fixed(&res); 141 + w.writeStruct(self, .big) catch unreachable; 142 + return res; 143 + } 132 144 }; 133 145 134 - // const ip: std.Io.net.IpAddress = .{ .ip4 = .unspecified(0) }; 135 - // const socket = try ip.bind(init.io, .{ .mode = .rdm, .protocol = .raw }); 136 - // defer socket.close(init.io); 146 + const headers: EthIpUdp = .{ 147 + .src_mac = @splat(0x0e), 148 + .id = 0, 149 + .src_addr = 0, 150 + .dst_addr = @bitCast([_]u8{ 255, 255, 255, 255 }), 151 + .src_port = undefined, // TODO: change this? 152 + .dst_port = 8888, 137 153 138 - // try socket.send(init.io, &.{ .ip4 = .{ .bytes = @splat(255), .port = 8888 } }, "foo"); 154 + .total_length = undefined, 155 + .udp_len = undefined, 156 + }; 157 + std.debug.print("headers: {any}\n", .{&headers.toBytes()}); 139 158 140 - // var sock_buffer: [1500]u8 = undefined; 141 - // var raw_socket_writer: RawSocketWriter = try .init("enp7s0", &sock_buffer); // /proc/net/dev 142 - // var net_buffer: [1500]u8 = undefined; 143 - // var net_writer: NetWriter = try .init(&raw_socket_writer.interface, &net_buffer); 144 - // var client = try SaprusClient.init(&net_writer.interface); 145 - // defer client.deinit(); 159 + const relay: SaprusMessage = .{ 160 + .relay = .{ 161 + .dest = .fromBytes(&parseDest(flags.dest)), 162 + .payload = flags.relay.?, 163 + }, 164 + }; 146 165 147 - // if (res.args.relay) |r| { 148 - // const dest = parseDest(res.args.dest); 149 - // try client.sendRelay( 150 - // if (r.len > 0) r else "Hello darkness my old friend", 151 - // dest, 152 - // ); 153 - // return; 154 - // } else if (res.args.connect) |c| { 155 - // if (false) { 156 - // _ = client.connect(if (c.len > 0) c else "Hello darkness my old friend") catch |err| switch (err) { 157 - // error.WouldBlock => null, 158 - // else => return err, 159 - // }; 160 - // return; 161 - // } 162 - // @panic("Not implemented"); 163 - // } 164 - 165 - // return clap.helpToFile(.stderr(), clap.Help, &params, .{}); 166 + var relay_bytes: [2048]u8 = undefined; 167 + std.debug.print("payload: {any}\n", .{relay.toBytes(&relay_bytes)}); 166 168 } 167 169 168 170 fn parseDest(in: ?[]const u8) [4]u8 { ··· 189 191 const SaprusMessage = zaprus.Message; 190 192 const RawSocketWriter = zaprus.RawSocketWriter; 191 193 192 - // Import C headers for network constants and structs 193 - const c = @cImport({ 194 - @cInclude("sys/socket.h"); 195 - @cInclude("linux/if_packet.h"); 196 - @cInclude("net/ethernet.h"); 197 - @cInclude("sys/ioctl.h"); 198 - @cInclude("net/if.h"); 199 - @cInclude("arpa/inet.h"); 200 - }); 201 - 202 194 const AF = std.os.linux.AF; 203 195 const SOCK = std.os.linux.SOCK; 204 - // const NetWriter = zaprus.NetWriter; 196 + 197 + const RawSocket = struct { 198 + fd: i32, 199 + sockaddr_ll: std.posix.sockaddr.ll, 200 + 201 + fn init(ifname: []const u8) RawSocket { 202 + const socket: i32 = @intCast(std.os.linux.socket(AF.PACKET, SOCK.RAW, 0)); 203 + 204 + var ifr: std.posix.ifreq = std.mem.zeroInit(std.posix.ifreq, .{}); 205 + @memcpy(ifr.ifrn.name[0..ifname.len], ifname); 206 + ifr.ifrn.name[ifname.len] = 0; 207 + try std.posix.ioctl_SIOCGIFINDEX(socket, &ifr); 208 + const ifindex: i32 = ifr.ifru.ivalue; 209 + 210 + var rval = std.posix.errno(std.os.linux.ioctl(socket, std.os.linux.SIOCGIFFLAGS, @intFromPtr(&ifr))); 211 + switch (rval) { 212 + .SUCCESS => {}, 213 + else => { 214 + return error.NicError; 215 + }, 216 + } 217 + ifr.ifru.flags.BROADCAST = true; 218 + ifr.ifru.flags.PROMISC = true; 219 + rval = std.posix.errno(std.os.linux.ioctl(socket, std.os.linux.SIOCSIFFLAGS, @intFromPtr(&ifr))); 220 + switch (rval) { 221 + .SUCCESS => {}, 222 + else => { 223 + return error.NicError; 224 + }, 225 + } 226 + std.debug.print("ifindex: {}\n", .{ifindex}); 227 + const sockaddr_ll = std.posix.sockaddr.ll{ 228 + .family = std.posix.AF.PACKET, 229 + .ifindex = ifindex, 230 + .protocol = std.mem.nativeToBig(u16, @as(u16, std.os.linux.ETH.P.IP)), 231 + .halen = 0, //not used 232 + .addr = .{ 0, 0, 0, 0, 0, 0, 0, 0 }, //not used 233 + .pkttype = 0, //not used 234 + .hatype = 0, //not used 235 + }; 236 + _ = std.os.linux.bind(socket, @ptrCast(&sockaddr_ll), @sizeOf(@TypeOf(sockaddr_ll))); 237 + 238 + return .{ 239 + .fd = socket, 240 + .sockaddr_ll = sockaddr_ll, 241 + }; 242 + } 243 + 244 + fn deinit() void {} 245 + 246 + fn send(self: RawSocket, payload: []const u8) !void { 247 + const sent_bytes = std.os.linux.sendto( 248 + self.fd, 249 + payload.ptr, 250 + payload.len, 251 + 0, 252 + @ptrCast(&self.sockaddr_ll), 253 + @sizeOf(@TypeOf(self.sockaddr_ll)), 254 + ); 255 + std.debug.assert(sent_bytes == payload.len); 256 + } 257 + 258 + fn receive(self: RawSocket, buf: []u8) ![]u8 { 259 + const len = std.os.linux.recvfrom( 260 + self.fd, 261 + buf.ptr, 262 + buf.len, 263 + 0, // flags 264 + null, 265 + null, 266 + ); 267 + return buf[0..len]; 268 + } 269 + }; 270 + 271 + const Writer = std.Io.Writer;
+6
src/message.zig
··· 22 22 23 23 pub const Relay = message.Relay; 24 24 pub const Connection = message.Connection; 25 + 26 + pub fn toBytes(self: message.Message, buf: []u8) []u8 { 27 + return switch (self) { 28 + inline else => |m| m.toBytes(buf), 29 + }; 30 + } 25 31 }; 26 32 27 33 pub const relay_dest_len = 4;