mirror of
https://github.com/Astatin3/unshell.git
synced 2026-06-08 22:38:01 -06:00
fcb3b2be17
- Write PROTOCOL.md with full wire format spec and 8 real-world scenario
analyses (reconnect, multi-operator, large files, AV evasion, router crash,
malformed packets, future pivoting)
- Rewrite workspace structure:
- unshell lib: protocol types (PacketHeader, TreeRequest/Response,
HandshakeMessage/Ack), Transport trait, TcpTransport, Tree routing
- ush-router: router binary with per-node threads, NodeRegistry with
longest-prefix path matching, packet relay
- ush-payload: implant binary with reconnect loop, module tree, InfoModule
- ush-cli: operator REPL with rustyline, session management, command parser
- Protocol design: two-part rkyv frame [header][payload]; router reads only
header for routing, payload bytes forwarded opaque
- All code documented with doc comments and examples
- Zero warnings, zero errors across entire workspace
- 32 tests pass (unit tests for tree routing, TCP transport, framing,
command parsing, node registry)
89 lines
2.8 KiB
Rust
89 lines
2.8 KiB
Rust
//! # Info Module
|
|
//!
|
|
//! Provides basic system information about the target at `/info`.
|
|
//!
|
|
//! ## Supported requests
|
|
//!
|
|
//! | Path | RequestType | Returns |
|
|
//! |---|---|---|
|
|
//! | `/info` | `Read` | UTF-8 string: OS name, arch, hostname |
|
|
//! | `/info` | `GetProcedures` | List of available procedures |
|
|
//!
|
|
//! ## Example
|
|
//!
|
|
//! From the operator CLI:
|
|
//! ```text
|
|
//! unshell [agents/abc123]> read info
|
|
//! linux x86_64 hostname=target-machine
|
|
//! ```
|
|
|
|
use unshell::protocol::{
|
|
content, ProcedureDescriptor, RequestType, ResponseStatus, TreeRequest, TreeResponse,
|
|
};
|
|
use unshell::tree::Endpoint;
|
|
|
|
/// Returns basic system information about the target host.
|
|
pub struct InfoModule;
|
|
|
|
impl Endpoint for InfoModule {
|
|
fn handle(&mut self, request: TreeRequest) -> TreeResponse {
|
|
match request.request_type {
|
|
RequestType::Read => handle_read(request),
|
|
RequestType::GetProcedures => handle_get_procedures(request),
|
|
_ => TreeResponse {
|
|
request_id: request.request_id,
|
|
status: ResponseStatus::UnsupportedOperation,
|
|
content_type: content::NONE.to_owned(),
|
|
data: Vec::new(),
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Return a one-line system summary.
|
|
fn handle_read(request: TreeRequest) -> TreeResponse {
|
|
let os = std::env::consts::OS;
|
|
let arch = std::env::consts::ARCH;
|
|
let hostname = hostname();
|
|
let info = format!("os={os} arch={arch} hostname={hostname}");
|
|
TreeResponse {
|
|
request_id: request.request_id,
|
|
status: ResponseStatus::Ok,
|
|
content_type: content::UTF8_STRING.to_owned(),
|
|
data: info.into_bytes(),
|
|
}
|
|
}
|
|
|
|
/// Return a list of procedures this module supports.
|
|
fn handle_get_procedures(request: TreeRequest) -> TreeResponse {
|
|
let procedures = vec![ProcedureDescriptor {
|
|
name: "read".to_owned(),
|
|
description: "Returns os, arch, and hostname of this target".to_owned(),
|
|
}];
|
|
|
|
let Ok(payload) = rkyv::to_bytes::<rkyv::rancor::Error>(&procedures) else {
|
|
return TreeResponse {
|
|
request_id: request.request_id,
|
|
status: ResponseStatus::ExecutionError,
|
|
content_type: content::NONE.to_owned(),
|
|
data: Vec::new(),
|
|
};
|
|
};
|
|
|
|
TreeResponse {
|
|
request_id: request.request_id,
|
|
status: ResponseStatus::Ok,
|
|
content_type: content::PROCEDURE_LIST.to_owned(),
|
|
data: payload.to_vec(),
|
|
}
|
|
}
|
|
|
|
/// Get the system hostname, or "unknown" if unavailable.
|
|
fn hostname() -> String {
|
|
// std::net::IpAddr doesn't give us hostname; use /etc/hostname or gethostname
|
|
// For now, use a simple approach that doesn't require extra deps.
|
|
std::fs::read_to_string("/etc/hostname")
|
|
.map(|s| s.trim().to_owned())
|
|
.unwrap_or_else(|_| "unknown".to_owned())
|
|
}
|