Adversarial C2 Protocol Implemented in Zig
0
fork

Configure Feed

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

Mostly done with parsing and serializing messages

+137 -322
+137 -321
src/message.zig
··· 2 2 /// This is the first value in the actual packet sent over the network. 3 3 pub const PacketType = enum(u16) { 4 4 relay = 0x003C, 5 - file_transfer = 0x8888, 6 5 connection = 0x00E9, 7 6 _, 8 7 }; 9 8 10 - /// Reserved option values. 11 - /// Currently unused. 12 - pub const ConnectionOptions = packed struct(u8) { 13 - opt1: bool = false, 14 - opt2: bool = false, 15 - opt3: bool = false, 16 - opt4: bool = false, 17 - opt5: bool = false, 18 - opt6: bool = false, 19 - opt7: bool = false, 20 - opt8: bool = false, 21 - }; 22 - 23 9 pub const MessageTypeError = error{ 24 10 NotImplementedSaprusType, 25 11 UnknownSaprusType, ··· 28 14 InvalidMessage, 29 15 }; 30 16 31 - pub const RelayMessage = struct { 17 + const message = @This(); 18 + 19 + pub const Message = union(PacketType) { 20 + relay: Message.Relay, 21 + connection: Message.Connection, 22 + 23 + pub const Relay = message.Relay; 24 + pub const Connection = message.Connection; 25 + }; 26 + 27 + pub const relay_dest_len = 4; 28 + 29 + pub fn parse(bytes: []const u8) MessageParseError!Message { 30 + var in: Reader = .fixed(bytes); 31 + const @"type" = in.takeEnum(PacketType, .big) catch |err| switch (err) { 32 + error.InvalidEnumTag => return error.UnknownSaprusType, 33 + else => return error.InvalidMessage, 34 + }; 35 + const checksum = in.takeArray(2) catch return error.InvalidMessage; 36 + switch (@"type") { 37 + .relay => { 38 + const dest: Relay.Dest = .fromBytes( 39 + in.takeArray(relay_dest_len) catch return error.InvalidMessage, 40 + ); 41 + const payload = in.buffered(); 42 + return .{ 43 + .relay = .{ 44 + .dest = dest, 45 + .checksum = checksum.*, 46 + .payload = payload, 47 + }, 48 + }; 49 + }, 50 + .connection => { 51 + const src = in.takeInt(u16, .big) catch return error.InvalidMessage; 52 + const dest = in.takeInt(u16, .big) catch return error.InvalidMessage; 53 + const seq = in.takeInt(u32, .big) catch return error.InvalidMessage; 54 + const id = in.takeInt(u32, .big) catch return error.InvalidMessage; 55 + const reserved = in.takeByte() catch return error.InvalidMessage; 56 + const options = in.takeStruct(Connection.Options, .big) catch return error.InvalidMessage; 57 + const payload = in.buffered(); 58 + return .{ 59 + .connection = .{ 60 + .src = src, 61 + .dest = dest, 62 + .seq = seq, 63 + .id = id, 64 + .reserved = reserved, 65 + .options = options, 66 + .payload = payload, 67 + }, 68 + }; 69 + }, 70 + else => return error.NotImplementedSaprusType, 71 + } 72 + } 73 + 74 + test parse { 75 + _ = try parse(&[_]u8{ 0x00, 0x3c, 0x00, 0x17, 0xac, 0x12, 0x01, 0x1e, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x20, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x20, 0x6c, 0x6f, 0x67, 0x67, 0x65, 0x64 }); 76 + 77 + { 78 + const expected: Message = .{ 79 + .connection = .{ 80 + .src = 12416, 81 + .dest = 61680, 82 + .seq = 0, 83 + .id = 0, 84 + .reserved = 0, 85 + .options = @bitCast(@as(u8, 100)), 86 + .payload = &[_]u8{ 0x69, 0x61, 0x6d, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74 }, 87 + }, 88 + }; 89 + const actual = try parse(&[_]u8{ 0x00, 0xe9, 0x00, 0x18, 0x30, 0x80, 0xf0, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x69, 0x61, 0x6d, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74 }); 90 + 91 + try std.testing.expectEqualDeep(expected, actual); 92 + } 93 + } 94 + 95 + const Relay = struct { 32 96 dest: Dest, 97 + checksum: [2]u8 = undefined, 33 98 payload: []const u8, 34 99 35 - pub const @"type": PacketType = .relay; 36 100 pub const Dest = struct { 37 - bytes: [4]u8, 101 + bytes: [relay_dest_len]u8, 38 102 39 103 /// Asserts bytes is less than or equal to 4 bytes 40 104 pub fn fromBytes(bytes: []const u8) Dest { ··· 45 109 } 46 110 }; 47 111 48 - pub fn init(dest: Dest, payload: []const u8) RelayMessage { 112 + pub fn init(dest: Dest, payload: []const u8) Relay { 49 113 return .{ .dest = dest, .payload = payload }; 50 114 } 51 115 52 116 /// Asserts that buf is large enough to fit the relay message. 53 - pub fn toBytes(self: RelayMessage, buf: []u8) []u8 { 117 + pub fn toBytes(self: Relay, buf: []u8) []u8 { 54 118 var out: Writer = .fixed(buf); 55 - out.writeInt(u16, @intFromEnum(RelayMessage.type), .big) catch unreachable; 56 - out.writeInt(u16, @intCast(self.payload.len + self.dest.bytes.len), .big) catch unreachable; 119 + out.writeInt(u16, @intFromEnum(PacketType.relay), .big) catch unreachable; 120 + out.writeInt(u16, undefined, .big) catch unreachable; // Length field, but unread. Will switch to checksum 57 121 out.writeAll(&self.dest.bytes) catch unreachable; 58 122 out.writeAll(self.payload) catch unreachable; 59 123 return out.buffered(); 60 124 } 61 125 62 - pub fn fromBytes(buf: []const u8) RelayMessage { 63 - var in: Reader = .fixed(buf); 64 - } 65 - 66 126 test toBytes { 67 127 var buf: [1024]u8 = undefined; 68 - const relay: RelayMessage = .init( 128 + const relay: Relay = .init( 69 129 .fromBytes(&.{ 172, 18, 1, 30 }), 70 130 // zig fmt: off 71 131 &[_]u8{ ··· 81 141 0x6e, 0x74, 0x20, 0x6c, 0x6f, 0x67, 0x67, 0x65, 0x64 82 142 }; 83 143 // zig fmt: on 84 - try std.testing.expectEqualSlices(u8, &expected, relay.toBytes(&buf)); 144 + try expectEqualMessageBuffers(&expected, relay.toBytes(&buf)); 85 145 } 86 146 }; 87 147 88 - // pub const ConnectionMessage = struct { 89 - // length: u16, 90 - // src_port: u16, 91 - // dest_port: u16, 92 - // seq_num: u32 = 0, 93 - // msg_id: u32 = 0, 94 - // // _reserved: u8 = 0, 95 - // options: ConnectionOptions = .{}, 96 - // payload: []u8, 97 - 98 - // pub const type: PacketType = .connection; 99 - 100 - // /// Asserts that buf is large enough to fit the connection message. 101 - // fn toBytes(self: ConnectionMessage, buf: []u8) []u8 { 102 - // var out: Writer = .fixed(buf); 103 - // out.writeInt(u16, ConnectionMessage.type, .big) catch unreachable; 104 - // return w.buffered(); 105 - // } 106 - // }; 107 - 108 - // // ZERO COPY STUFF 109 - // // &payload could be a void value that is treated as a pointer to a [*]u8 110 - // /// All Saprus messages 111 - // pub const Message = packed struct { 112 - // pub const Relay = packed struct { 113 - // dest: @Vector(4, u8), 114 - // payload: void, 115 - 116 - // pub fn getPayload(self: *align(1) Relay) []u8 { 117 - // const len: *u16 = @ptrFromInt(@intFromPtr(self) - @sizeOf(u16)); 118 - // return @as([*]u8, @ptrCast(&self.payload))[0 .. len.* - @bitSizeOf(Relay) / 8]; 119 - // } 120 - // }; 121 - // pub const Connection = packed struct { 122 - // src_port: u16, // random number > 1024 123 - // dest_port: u16, // random number > 1024 124 - // seq_num: u32 = 0, 125 - // msg_id: u32 = 0, 126 - // reserved: u8 = 0, 127 - // options: ConnectionOptions = .{}, 128 - // payload: void, 129 - 130 - // pub fn getPayload(self: *align(1) Connection) []u8 { 131 - // const len: *u16 = @ptrFromInt(@intFromPtr(self) - @sizeOf(u16)); 132 - // return @as([*]u8, @ptrCast(&self.payload))[0 .. len.* - @bitSizeOf(Connection) / 8]; 133 - // } 134 - 135 - // fn nativeFromNetworkEndian(self: *align(1) Connection) void { 136 - // self.src_port = bigToNative(@TypeOf(self.src_port), self.src_port); 137 - // self.dest_port = bigToNative(@TypeOf(self.dest_port), self.dest_port); 138 - // self.seq_num = bigToNative(@TypeOf(self.seq_num), self.seq_num); 139 - // self.msg_id = bigToNative(@TypeOf(self.msg_id), self.msg_id); 140 - // } 141 - 142 - // fn networkFromNativeEndian(self: *align(1) Connection) void { 143 - // self.src_port = nativeToBig(@TypeOf(self.src_port), self.src_port); 144 - // self.dest_port = nativeToBig(@TypeOf(self.dest_port), self.dest_port); 145 - // self.seq_num = nativeToBig(@TypeOf(self.seq_num), self.seq_num); 146 - // self.msg_id = nativeToBig(@TypeOf(self.msg_id), self.msg_id); 147 - // } 148 - // }; 149 - 150 - // const Self = @This(); 151 - // const SelfBytes = []align(1) u8; 152 - 153 - // type: PacketType, 154 - // length: u16, 155 - // bytes: void = {}, 156 - 157 - // /// Takes a byte slice, and returns a Message struct backed by the slice. 158 - // /// This properly initializes the top level headers within the slice. 159 - // /// This is used for creating new messages. For reading messages from the network, 160 - // /// see: networkBytesAsValue. 161 - // pub fn init(@"type": PacketType, bytes: []u8) *align(1) Self { 162 - // std.debug.assert(bytes.len >= @sizeOf(Self)); 163 - // const res: *align(1) Self = @ptrCast(bytes.ptr); 164 - // res.type = @"type"; 165 - // res.length = @intCast(bytes.len - @sizeOf(Self)); 166 - // return res; 167 - // } 168 - 169 - // /// Compute the number of bytes required to store a given payload size for a given message type. 170 - // pub fn calcSize(comptime @"type": PacketType, payload_len: usize) MessageTypeError!u16 { 171 - // const header_size = @bitSizeOf(switch (@"type") { 172 - // .relay => Relay, 173 - // .connection => Connection, 174 - // .file_transfer => return MessageTypeError.NotImplementedSaprusType, 175 - // else => return MessageTypeError.UnknownSaprusType, 176 - // }) / 8; 177 - // return @intCast(payload_len + @sizeOf(Self) + header_size); 178 - // } 179 - 180 - // fn getRelay(self: *align(1) Self) *align(1) Relay { 181 - // return std.mem.bytesAsValue(Relay, &self.bytes); 182 - // } 183 - // fn getConnection(self: *align(1) Self) *align(1) Connection { 184 - // return std.mem.bytesAsValue(Connection, &self.bytes); 185 - // } 186 - 187 - // /// Access the message Saprus payload. 188 - // pub fn getSaprusTypePayload(self: *align(1) Self) MessageTypeError!(union(PacketType) { 189 - // relay: *align(1) Relay, 190 - // file_transfer: void, 191 - // connection: *align(1) Connection, 192 - // }) { 193 - // return switch (self.type) { 194 - // .relay => .{ .relay = self.getRelay() }, 195 - // .connection => .{ .connection = self.getConnection() }, 196 - // .file_transfer => MessageTypeError.NotImplementedSaprusType, 197 - // else => MessageTypeError.UnknownSaprusType, 198 - // }; 199 - // } 200 - 201 - // /// Convert the message to native endianness from network endianness in-place. 202 - // pub fn nativeFromNetworkEndian(self: *align(1) Self) MessageTypeError!void { 203 - // self.type = @enumFromInt(bigToNative( 204 - // @typeInfo(@TypeOf(self.type)).@"enum".tag_type, 205 - // @intFromEnum(self.type), 206 - // )); 207 - // self.length = bigToNative(@TypeOf(self.length), self.length); 208 - // errdefer { 209 - // // If the payload specific headers fail, revert the top level header values 210 - // self.type = @enumFromInt(nativeToBig( 211 - // @typeInfo(@TypeOf(self.type)).@"enum".tag_type, 212 - // @intFromEnum(self.type), 213 - // )); 214 - // self.length = nativeToBig(@TypeOf(self.length), self.length); 215 - // } 216 - // switch (try self.getSaprusTypePayload()) { 217 - // .relay => {}, 218 - // .connection => |*con| con.*.nativeFromNetworkEndian(), 219 - // // We know other values are unreachable, 220 - // // because they would have returned an error from the switch condition. 221 - // else => unreachable, 222 - // } 223 - // } 148 + const Connection = struct { 149 + src: u16, 150 + dest: u16, 151 + seq: u32, 152 + id: u32, 153 + reserved: u8 = undefined, 154 + options: Options = undefined, 155 + payload: []const u8, 224 156 225 - // /// Convert the message to network endianness from native endianness in-place. 226 - // pub fn networkFromNativeEndian(self: *align(1) Self) MessageTypeError!void { 227 - // try switch (try self.getSaprusTypePayload()) { 228 - // .relay => {}, 229 - // .connection => |*con| con.*.networkFromNativeEndian(), 230 - // .file_transfer => MessageTypeError.NotImplementedSaprusType, 231 - // else => MessageTypeError.UnknownSaprusType, 232 - // }; 233 - // self.type = @enumFromInt(nativeToBig( 234 - // @typeInfo(@TypeOf(self.type)).@"enum".tag_type, 235 - // @intFromEnum(self.type), 236 - // )); 237 - // self.length = nativeToBig(@TypeOf(self.length), self.length); 238 - // } 157 + /// Reserved option values. 158 + /// Currently unused. 159 + pub const Options = packed struct(u8) { 160 + opt1: bool = false, 161 + opt2: bool = false, 162 + opt3: bool = false, 163 + opt4: bool = false, 164 + opt5: bool = false, 165 + opt6: bool = false, 166 + opt7: bool = false, 167 + opt8: bool = false, 168 + }; 239 169 240 - // /// Convert network endian bytes to a native endian value in-place. 241 - // pub fn networkBytesAsValue(bytes: SelfBytes) MessageParseError!*align(1) Self { 242 - // const res = std.mem.bytesAsValue(Self, bytes); 243 - // try res.nativeFromNetworkEndian(); 244 - // return .bytesAsValue(bytes); 245 - // } 170 + /// Asserts that buf is large enough to fit the connection message. 171 + pub fn toBytes(self: Connection, buf: []u8) []u8 { 172 + var out: Writer = .fixed(buf); 173 + out.writeInt(u16, @intFromEnum(PacketType.connection), .big) catch unreachable; 174 + out.writeInt(u16, undefined, .big) catch unreachable; // Saprus length field, unread. 175 + out.writeInt(u16, self.src, .big) catch unreachable; 176 + out.writeInt(u16, self.dest, .big) catch unreachable; 177 + out.writeInt(u32, self.seq, .big) catch unreachable; 178 + out.writeInt(u32, self.id, .big) catch unreachable; 179 + out.writeByte(self.reserved) catch unreachable; 180 + out.writeStruct(self.options, .big) catch unreachable; 181 + out.writeAll(self.payload) catch unreachable; 182 + return out.buffered(); 183 + } 184 + }; 246 185 247 - // /// Create a structured view of the bytes without initializing the length or type, 248 - // /// and without converting the endianness. 249 - // pub fn bytesAsValue(bytes: SelfBytes) MessageParseError!*align(1) Self { 250 - // const res = std.mem.bytesAsValue(Self, bytes); 251 - // return switch (res.type) { 252 - // .relay, .connection => if (bytes.len == res.length + @sizeOf(Self)) 253 - // res 254 - // else 255 - // MessageParseError.InvalidMessage, 256 - // .file_transfer => MessageParseError.NotImplementedSaprusType, 257 - // else => MessageParseError.UnknownSaprusType, 258 - // }; 259 - // } 186 + test "Round trip" { 187 + { 188 + const expected = [_]u8{ 0x0, 0xe9, 0x0, 0x15, 0x30, 0x80, 0xf0, 0xf0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x64, 0x36, 0x3a, 0x3a, 0x64, 0x61, 0x74, 0x61 }; 189 + const msg = (try parse(&expected)).connection; 190 + var res_buf: [expected.len + 1]u8 = undefined; // + 1 to test subslice result. 191 + const res = msg.toBytes(&res_buf); 192 + try expectEqualMessageBuffers(&expected, res); 193 + } 194 + } 260 195 261 - // /// Deprecated. 262 - // /// If I need the bytes, I should just pass around the slice that is backing this to begin with. 263 - // pub fn asBytes(self: *align(1) Self) SelfBytes { 264 - // const size = @sizeOf(Self) + self.length; 265 - // return @as([*]align(1) u8, @ptrCast(self))[0..size]; 266 - // } 267 - // }; 268 - 269 - // test "testing variable length zero copy struct" { 270 - // { 271 - // // Relay test 272 - // const payload = "Hello darkness my old friend"; 273 - // var msg_bytes: [try Message.calcSize(.relay, payload.len)]u8 align(@alignOf(Message)) = undefined; 274 - 275 - // // Create a view of the byte slice as a Message 276 - // const msg: *align(1) Message = .init(.relay, &msg_bytes); 277 - 278 - // { 279 - // // Set the message values 280 - // { 281 - // // These are both set by the init call. 282 - // // msg.type = .relay; 283 - // // msg.length = payload_len; 284 - // } 285 - // const relay = (try msg.getSaprusTypePayload()).relay; 286 - // relay.dest = .{ 1, 2, 3, 4 }; 287 - // @memcpy(relay.getPayload(), payload); 288 - // } 289 - 290 - // { 291 - // // Print the message as hex using the network byte order 292 - // try msg.networkFromNativeEndian(); 293 - // // We know the error from nativeFromNetworkEndian is unreachable because 294 - // // it would have returned an error from networkFromNativeEndian. 295 - // defer msg.nativeFromNetworkEndian() catch unreachable; 296 - // std.debug.print("relay network bytes: {x}\n", .{msg_bytes}); 297 - // std.debug.print("bytes len: {d}\n", .{msg_bytes.len}); 298 - // } 299 - 300 - // if (false) { 301 - // // Illegal behavior 302 - // std.debug.print("{any}\n", .{(try msg.getSaprusTypePayload()).connection}); 303 - // } 304 - 305 - // try std.testing.expectEqualDeep(msg, try Message.bytesAsValue(msg.asBytes())); 306 - // } 307 - 308 - // { 309 - // // Connection test 310 - // const payload = "Hello darkness my old friend"; 311 - // var msg_bytes: [try Message.calcSize(.connection, payload.len)]u8 align(@alignOf(Message)) = undefined; 312 - 313 - // // Create a view of the byte slice as a Message 314 - // const msg: *align(1) Message = .init(.connection, &msg_bytes); 315 - 316 - // { 317 - // // Initializing connection header values 318 - // const connection = (try msg.getSaprusTypePayload()).connection; 319 - // connection.src_port = 1; 320 - // connection.dest_port = 2; 321 - // connection.seq_num = 3; 322 - // connection.msg_id = 4; 323 - // connection.reserved = 5; 324 - // connection.options = @bitCast(@as(u8, 6)); 325 - // @memcpy(connection.getPayload(), payload); 326 - // } 327 - 328 - // { 329 - // // Print the message as hex using the network byte order 330 - // try msg.networkFromNativeEndian(); 331 - // // We know the error from nativeFromNetworkEndian is unreachable because 332 - // // it would have returned an error from networkFromNativeEndian. 333 - // defer msg.nativeFromNetworkEndian() catch unreachable; 334 - // std.debug.print("connection network bytes: {x}\n", .{msg_bytes}); 335 - // std.debug.print("bytes len: {d}\n", .{msg_bytes.len}); 336 - // } 337 - // } 338 - // } 196 + // Skip checking the length / checksum, because that is undefined. 197 + fn expectEqualMessageBuffers(expected: []const u8, actual: []const u8) !void { 198 + try std.testing.expectEqualSlices(u8, expected[0..2], actual[0..2]); 199 + try std.testing.expectEqualSlices(u8, expected[4..], actual[4..]); 200 + } 339 201 340 202 const std = @import("std"); 341 203 const Allocator = std.mem.Allocator; 342 204 const Writer = std.Io.Writer; 343 - 344 - // const asBytes = std.mem.asBytes; 345 - // const nativeToBig = std.mem.nativeToBig; 346 - // const bigToNative = std.mem.bigToNative; 347 - 348 - // test "Round trip Relay toBytes and fromBytes" { 349 - // if (true) return error.SkipZigTest; 350 - // const gpa = std.testing.allocator; 351 - // const msg = Message{ 352 - // .relay = .{ 353 - // .header = .{ .dest = .{ 255, 255, 255, 255 } }, 354 - // .payload = "Hello darkness my old friend", 355 - // }, 356 - // }; 357 - 358 - // const to_bytes = try msg.toBytes(gpa); 359 - // defer gpa.free(to_bytes); 360 - 361 - // const from_bytes = try Message.fromBytes(to_bytes, gpa); 362 - // defer from_bytes.deinit(gpa); 363 - 364 - // try std.testing.expectEqualDeep(msg, from_bytes); 365 - // } 366 - 367 - // test "Round trip Connection toBytes and fromBytes" { 368 - // if (false) { 369 - // const gpa = std.testing.allocator; 370 - // const msg = Message{ 371 - // .connection = .{ 372 - // .header = .{ 373 - // .src_port = 0, 374 - // .dest_port = 0, 375 - // }, 376 - // .payload = "Hello darkness my old friend", 377 - // }, 378 - // }; 379 - 380 - // const to_bytes = try msg.toBytes(gpa); 381 - // defer gpa.free(to_bytes); 382 - 383 - // const from_bytes = try Message.fromBytes(to_bytes, gpa); 384 - // defer from_bytes.deinit(gpa); 385 - 386 - // try std.testing.expectEqualDeep(msg, from_bytes); 387 - // } 388 - // return error.SkipZigTest; 389 - // } 205 + const Reader = std.Io.Reader; 390 206 391 207 test { 392 208 std.testing.refAllDeclsRecursive(@This());
-1
src/root.zig
··· 4 4 const msg = @import("message.zig"); 5 5 6 6 pub const PacketType = msg.PacketType; 7 - pub const ConnectionOptions = msg.ConnectionOptions; 8 7 pub const MessageTypeError = msg.MessageTypeError; 9 8 pub const MessageParseError = msg.MessageParseError; 10 9 pub const Message = msg.Message;