mirror of
https://git.robbyzambito.me/zaprus
synced 2026-05-06 22:30:37 +00:00
Compare commits
1 Commits
91fdb2c6f8
...
0.2.0
| Author | SHA1 | Date | |
|---|---|---|---|
| d1ca448835 |
@@ -1,26 +0,0 @@
|
|||||||
when:
|
|
||||||
- event: ["push", "pull_request", "manual"]
|
|
||||||
branch: ["dev", "master"]
|
|
||||||
tag: ["v*"]
|
|
||||||
|
|
||||||
engine: "nixery"
|
|
||||||
|
|
||||||
dependencies:
|
|
||||||
nixpkgs:
|
|
||||||
- rclone
|
|
||||||
git+https://github.com/mitchellh/zig-overlay:
|
|
||||||
- master
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: "Build"
|
|
||||||
command: "zig build -Doptimize=ReleaseSmall -Dcpu=baseline"
|
|
||||||
- name: "Publish"
|
|
||||||
command: |
|
|
||||||
rclone sync ./zig-out \
|
|
||||||
--webdav-url "$RELEASE_NEXTCLOUD_HOST/remote.php/dav/files/$RELEASE_NEXTCLOUD_USER/" \
|
|
||||||
--webdav-user "$RELEASE_NEXTCLOUD_USER" \
|
|
||||||
--webdav-pass "$RELEASE_NEXTCLOUD_PASS" \
|
|
||||||
--webdav-vendor nextcloud \
|
|
||||||
:webdav:"zaprus/zaprus-$TANGLED_REF_NAME" \
|
|
||||||
-q
|
|
||||||
|
|
||||||
@@ -3,9 +3,4 @@
|
|||||||
This is an implementation of the [Saprus protocol](https://gitlab.com/c2-games/red-team/saprus) in Zig.
|
This is an implementation of the [Saprus protocol](https://gitlab.com/c2-games/red-team/saprus) in Zig.
|
||||||
It is useful for developing clients either in Zig, or in any other language using the C bindings.
|
It is useful for developing clients either in Zig, or in any other language using the C bindings.
|
||||||
|
|
||||||
Binary releases can be downloaded [here](https://cloud.zambito.xyz/s/7jJPTm68Zp3mN8F).
|
Binary releases can be downloaded [here](https://cloud.zambito.xyz/s/cNaLeDz38W5ZcZs).
|
||||||
|
|
||||||
The code for this can be found here:
|
|
||||||
|
|
||||||
- https://tangled.org/zambyte.robbyzambito.me/zaprus
|
|
||||||
- https://git.robbyzambito.me/zaprus
|
|
||||||
|
|||||||
@@ -7,9 +7,6 @@ const std = @import("std");
|
|||||||
// build runner to parallelize the build automatically (and the cache system to
|
// build runner to parallelize the build automatically (and the cache system to
|
||||||
// know when a step doesn't need to be re-run).
|
// know when a step doesn't need to be re-run).
|
||||||
pub fn build(b: *std.Build) void {
|
pub fn build(b: *std.Build) void {
|
||||||
// Ensure the license is included in the output directory
|
|
||||||
b.installFile("LICENSE.md", "LICENSE.md");
|
|
||||||
b.installFile("README.md", "README.md");
|
|
||||||
// Standard target options allow the person running `zig build` to choose
|
// Standard target options allow the person running `zig build` to choose
|
||||||
// what target to build for. Here we do not override the defaults, which
|
// what target to build for. Here we do not override the defaults, which
|
||||||
// means any target is allowed, and the default is native. Other options
|
// means any target is allowed, and the default is native. Other options
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
// This is a [Semantic Version](https://semver.org/).
|
// This is a [Semantic Version](https://semver.org/).
|
||||||
// In a future version of Zig it will be used for package deduplication.
|
// In a future version of Zig it will be used for package deduplication.
|
||||||
.version = "0.0.0",
|
.version = "0.2.0",
|
||||||
|
|
||||||
// Together with name, this represents a globally unique package
|
// Together with name, this represents a globally unique package
|
||||||
// identifier. This field is generated by the Zig toolchain when the
|
// identifier. This field is generated by the Zig toolchain when the
|
||||||
|
|||||||
@@ -46,11 +46,11 @@ pub fn sendRelay(self: *Client, io: Io, payload: []const u8, dest: [4]u8) !void
|
|||||||
const rand = io_source.interface();
|
const rand = io_source.interface();
|
||||||
|
|
||||||
var headers: EthIpUdp = .{
|
var headers: EthIpUdp = .{
|
||||||
.src_mac = .fromBytes(self.socket.mac),
|
.src_mac = self.socket.mac,
|
||||||
.ip = .{
|
.ip = .{
|
||||||
.id = rand.int(u16),
|
.id = rand.int(u16),
|
||||||
.src_addr = .fromBytes(.{ 0, 0, 0, 0 }), //rand.int(u32),
|
.src_addr = 0, //rand.int(u32),
|
||||||
.dst_addr = .fromBytes(.{ 255, 255, 255, 255 }),
|
.dst_addr = @bitCast([_]u8{ 255, 255, 255, 255 }),
|
||||||
.len = undefined,
|
.len = undefined,
|
||||||
},
|
},
|
||||||
.udp = .{
|
.udp = .{
|
||||||
@@ -86,11 +86,11 @@ pub fn connect(self: Client, io: Io, payload: []const u8) (error{ BpfAttachFaile
|
|||||||
const rand = io_source.interface();
|
const rand = io_source.interface();
|
||||||
|
|
||||||
var headers: EthIpUdp = .{
|
var headers: EthIpUdp = .{
|
||||||
.src_mac = .fromBytes(self.socket.mac),
|
.src_mac = self.socket.mac,
|
||||||
.ip = .{
|
.ip = .{
|
||||||
.id = rand.int(u16),
|
.id = rand.int(u16),
|
||||||
.src_addr = .fromBytes(.{ 0, 0, 0, 0 }), //rand.int(u32),
|
.src_addr = 0, //rand.int(u32),
|
||||||
.dst_addr = .fromBytes(.{ 255, 255, 255, 255 }),
|
.dst_addr = @bitCast([_]u8{ 255, 255, 255, 255 }),
|
||||||
.len = undefined,
|
.len = undefined,
|
||||||
},
|
},
|
||||||
.udp = .{
|
.udp = .{
|
||||||
|
|||||||
@@ -14,28 +14,6 @@
|
|||||||
// You should have received a copy of the GNU General Public License along with
|
// You should have received a copy of the GNU General Public License along with
|
||||||
// Zaprus. If not, see <https://www.gnu.org/licenses/>.
|
// Zaprus. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
pub const IpAddr = packed struct {
|
|
||||||
int: I,
|
|
||||||
|
|
||||||
const V = @Vector(4, u8);
|
|
||||||
const I = u32;
|
|
||||||
|
|
||||||
pub fn fromBytes(s: V) IpAddr {
|
|
||||||
return .{ .int = @bitCast(s) };
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const MacAddr = packed struct {
|
|
||||||
int: I,
|
|
||||||
|
|
||||||
const V = @Vector(6, u8);
|
|
||||||
const I = @Int(.unsigned, @bitSizeOf(V));
|
|
||||||
|
|
||||||
pub fn fromBytes(s: V) MacAddr {
|
|
||||||
return .{ .int = @bitCast(s) };
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const EthIpUdp = packed struct(u336) { // 42 bytes * 8 bits = 336
|
pub const EthIpUdp = packed struct(u336) { // 42 bytes * 8 bits = 336
|
||||||
// --- UDP (Last in memory, defined first for LSB->MSB) ---
|
// --- UDP (Last in memory, defined first for LSB->MSB) ---
|
||||||
udp: packed struct {
|
udp: packed struct {
|
||||||
@@ -47,8 +25,8 @@ pub const EthIpUdp = packed struct(u336) { // 42 bytes * 8 bits = 336
|
|||||||
|
|
||||||
// --- IP ---
|
// --- IP ---
|
||||||
ip: packed struct {
|
ip: packed struct {
|
||||||
dst_addr: IpAddr,
|
dst_addr: u32,
|
||||||
src_addr: IpAddr,
|
src_addr: u32,
|
||||||
header_checksum: u16 = 0,
|
header_checksum: u16 = 0,
|
||||||
protocol: u8 = 17, // udp
|
protocol: u8 = 17, // udp
|
||||||
ttl: u8 = 0x40,
|
ttl: u8 = 0x40,
|
||||||
@@ -75,8 +53,8 @@ pub const EthIpUdp = packed struct(u336) { // 42 bytes * 8 bits = 336
|
|||||||
|
|
||||||
// --- Ethernet ---
|
// --- Ethernet ---
|
||||||
eth_type: u16 = std.os.linux.ETH.P.IP,
|
eth_type: u16 = std.os.linux.ETH.P.IP,
|
||||||
src_mac: MacAddr,
|
src_mac: @Vector(6, u8),
|
||||||
dst_mac: MacAddr = .fromBytes(@splat(0xff)),
|
dst_mac: @Vector(6, u8) = @splat(0xff),
|
||||||
|
|
||||||
pub fn toBytes(self: @This()) [336 / 8]u8 {
|
pub fn toBytes(self: @This()) [336 / 8]u8 {
|
||||||
var res: [336 / 8]u8 = undefined;
|
var res: [336 / 8]u8 = undefined;
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ pub fn init() error{
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setTimeout(self: *RawSocket, sec: isize, usec: i64) error{SetTimeoutError}!void {
|
pub fn setTimeout(self: *RawSocket, sec: isize, usec: i64) !void {
|
||||||
const timeout: std.os.linux.timeval = .{ .sec = sec, .usec = usec };
|
const timeout: std.os.linux.timeval = .{ .sec = sec, .usec = usec };
|
||||||
const timeout_ret = std.os.linux.setsockopt(self.fd, std.os.linux.SOL.SOCKET, std.os.linux.SO.RCVTIMEO, @ptrCast(&timeout), @sizeOf(@TypeOf(timeout)));
|
const timeout_ret = std.os.linux.setsockopt(self.fd, std.os.linux.SOL.SOCKET, std.os.linux.SO.RCVTIMEO, @ptrCast(&timeout), @sizeOf(@TypeOf(timeout)));
|
||||||
if (timeout_ret != 0) return error.SetTimeoutError;
|
if (timeout_ret != 0) return error.SetTimeoutError;
|
||||||
|
|||||||
71
src/main.zig
71
src/main.zig
@@ -146,54 +146,28 @@ pub fn main(init: std.process.Init) !void {
|
|||||||
|
|
||||||
if (flags.connect != null) {
|
if (flags.connect != null) {
|
||||||
reconnect: while (true) {
|
reconnect: while (true) {
|
||||||
client = SaprusClient.init() catch |err| switch (err) {
|
client = try .init();
|
||||||
error.NoInterfaceFound => {
|
|
||||||
init.io.sleep(.fromMilliseconds(100), .boot) catch unreachable;
|
|
||||||
continue :reconnect;
|
|
||||||
},
|
|
||||||
else => |e| return e,
|
|
||||||
};
|
|
||||||
defer client.deinit();
|
defer client.deinit();
|
||||||
log.debug("Starting connection", .{});
|
log.debug("Starting connection", .{});
|
||||||
|
|
||||||
client.socket.setTimeout(if (is_debug) 3 else 25, 0) catch {
|
try client.socket.setTimeout(if (is_debug) 3 else 25, 0);
|
||||||
log.err("Unable to set timeout", .{});
|
|
||||||
init.io.sleep(.fromMilliseconds(100), .boot) catch unreachable;
|
|
||||||
continue :reconnect;
|
|
||||||
};
|
|
||||||
var connection = client.connect(init.io, w.buffered()) catch {
|
var connection = client.connect(init.io, w.buffered()) catch {
|
||||||
log.debug("Connection timed out", .{});
|
log.debug("Connection timed out", .{});
|
||||||
continue :reconnect;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
log.debug("Connection started", .{});
|
log.debug("Connection started", .{});
|
||||||
|
|
||||||
next_message: while (true) {
|
next_message: while (true) {
|
||||||
var res_buf: [2048]u8 = undefined;
|
var res_buf: [2048]u8 = undefined;
|
||||||
client.socket.setTimeout(if (is_debug) 60 else 600, 0) catch {
|
try client.socket.setTimeout(if (is_debug) 60 else 600, 0);
|
||||||
log.err("Unable to set timeout", .{});
|
|
||||||
init.io.sleep(.fromMilliseconds(100), .boot) catch unreachable;
|
|
||||||
continue :reconnect;
|
|
||||||
};
|
|
||||||
const next = connection.next(init.io, &res_buf) catch {
|
const next = connection.next(init.io, &res_buf) catch {
|
||||||
continue :reconnect;
|
continue :reconnect;
|
||||||
};
|
};
|
||||||
|
|
||||||
const b64d = std.base64.standard.Decoder;
|
const b64d = std.base64.standard.Decoder;
|
||||||
var connection_payload_buf: [2048]u8 = undefined;
|
var connection_payload_buf: [2048]u8 = undefined;
|
||||||
const connection_payload = blk: {
|
const connection_payload = connection_payload_buf[0..try b64d.calcSizeForSlice(next)];
|
||||||
const size = b64d.calcSizeForSlice(next) catch |err| switch (err) {
|
|
||||||
error.InvalidCharacter, error.InvalidPadding => {
|
|
||||||
log.warn("Invalid base64 message received, ignoring: '{s}'", .{next});
|
|
||||||
continue :next_message;
|
|
||||||
},
|
|
||||||
error.NoSpaceLeft => {
|
|
||||||
log.warn("No space left when decoding base64 string, ignoring.", .{});
|
|
||||||
continue :next_message;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
break :blk connection_payload_buf[0..size];
|
|
||||||
};
|
|
||||||
b64d.decode(connection_payload, next) catch {
|
b64d.decode(connection_payload, next) catch {
|
||||||
log.debug("Failed to decode message, skipping: '{s}'", .{connection_payload});
|
log.debug("Failed to decode message, skipping: '{s}'", .{connection_payload});
|
||||||
continue;
|
continue;
|
||||||
@@ -241,7 +215,7 @@ pub fn main(init: std.process.Init) !void {
|
|||||||
|
|
||||||
var is_killed: std.atomic.Value(bool) = .init(false);
|
var is_killed: std.atomic.Value(bool) = .init(false);
|
||||||
|
|
||||||
var kill_task = init.io.concurrent(killProcessAfter, .{ init.io, &child, .fromSeconds(3), &is_killed }) catch unreachable;
|
var kill_task = try init.io.concurrent(killProcessAfter, .{ init.io, &child, .fromSeconds(3), &is_killed });
|
||||||
defer _ = kill_task.cancel(init.io) catch {};
|
defer _ = kill_task.cancel(init.io) catch {};
|
||||||
|
|
||||||
var cmd_output_buf: [SaprusClient.max_payload_len * 2]u8 = undefined;
|
var cmd_output_buf: [SaprusClient.max_payload_len * 2]u8 = undefined;
|
||||||
@@ -264,41 +238,12 @@ pub fn main(init: std.process.Init) !void {
|
|||||||
break;
|
break;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const child_output_chunk = child_output_reader.interface.takeArray(child_output_buf.len) catch |err| switch (err) {
|
cmd_output.print("{b64}", .{try child_output_reader.interface.takeArray(child_output_buf.len)}) catch unreachable;
|
||||||
error.EndOfStream => {
|
|
||||||
log.warn("Reached end of stream when reading from the child process. Maybe this should be handled more gracefull, but ignoring for now.", .{});
|
|
||||||
continue :next_message;
|
|
||||||
},
|
|
||||||
error.ReadFailed => if (child_output_reader.err) |co_err| switch (co_err) {
|
|
||||||
error.AccessDenied,
|
|
||||||
error.ConnectionResetByPeer,
|
|
||||||
error.InputOutput,
|
|
||||||
error.IsDir,
|
|
||||||
error.LockViolation,
|
|
||||||
error.NotOpenForReading,
|
|
||||||
error.SocketUnconnected,
|
|
||||||
error.SystemResources,
|
|
||||||
error.WouldBlock,
|
|
||||||
=> |e| {
|
|
||||||
log.err("Unending error reading output from child process: {t}", .{e});
|
|
||||||
continue :next_message;
|
|
||||||
},
|
|
||||||
error.Canceled => |e| return e,
|
|
||||||
error.Unexpected => {
|
|
||||||
log.err("Unexpected error reading output from child process.", .{});
|
|
||||||
continue :next_message;
|
|
||||||
},
|
|
||||||
} else {
|
|
||||||
log.err("Shouldn't get here :(", .{});
|
|
||||||
continue :next_message;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
cmd_output.print("{b64}", .{child_output_chunk}) catch unreachable;
|
|
||||||
connection.send(init.io, .{}, cmd_output.buffered()) catch |err| {
|
connection.send(init.io, .{}, cmd_output.buffered()) catch |err| {
|
||||||
log.debug("Failed to send connection chunk: {t}", .{err});
|
log.debug("Failed to send connection chunk: {t}", .{err});
|
||||||
continue :next_message;
|
continue :next_message;
|
||||||
};
|
};
|
||||||
init.io.sleep(.fromMilliseconds(40), .boot) catch unreachable;
|
try init.io.sleep(.fromMilliseconds(40), .boot);
|
||||||
} else {
|
} else {
|
||||||
kill_task.cancel(init.io) catch {};
|
kill_task.cancel(init.io) catch {};
|
||||||
killProcessAfter(init.io, &child, .zero, &is_killed) catch |err| {
|
killProcessAfter(init.io, &child, .zero, &is_killed) catch |err| {
|
||||||
|
|||||||
Reference in New Issue
Block a user