You need to enable JavaScript to run this app. Rust反向Shell按上箭头无法实时回填历史命令解决方案咨询
优惠活动
大模型
产品
解决方案
定价
更多

Rust反向Shell按上箭头无法实时回填历史命令解决方案咨询

问题根因

默认Linux终端工作在规范模式(Canonical Mode):内核会缓存所有输入直到遇到回车才返回给应用,同时自动回显输入字符,方向键、退格等特殊按键由内核预处理,不会交给上层程序处理,因此你无法实时捕获上箭头按键实现历史命令回填。

之前使用rustyline的缺陷是它完全接管了行输入逻辑,自带自定义提示符,会覆盖远端Shell返回的[root@arch ~]$这类原生提示符。

实现方案

要实现rlwrap -cAr nc -lvnp 55100的同等效果,无需引入rustyline、也不需要本地存储历史,只需按以下步骤实现:

  1. 切换终端为原始模式(Raw Mode):禁用规范模式、默认回显、特殊字符预处理,每个按键按下后会实时提交给程序处理
  2. 内存级维护三个状态:当前输入行缓冲区、光标位置、历史命令队列(完全在内存存储,程序退出后自动销毁,不会写入本地磁盘)
  3. 手动处理所有按键事件:
    • 可打印字符:追加到输入缓冲区,手动回显到终端
    • 退格键:删除缓冲区末尾字符,输出\x08 \x08控制序列擦除终端上的对应字符
    • 上/下方向键:切换到对应历史命令后,先擦除当前行的已输入内容,再输出历史命令内容,同步更新当前输入缓冲区
    • 回车键:将当前缓冲区内容加\n发送给远端Shell,把输入内容加入历史队列,清空当前缓冲区
  4. 程序退出前强制恢复终端默认配置,避免终端行为异常。
完整实现代码

首先Cargo.toml依赖:

[package]
name = "reverse_shell_listener"
version = "0.1.0"
edition = "2021"

[dependencies]
nix = "0.26.2"
ctrlc = "3.4.0"

主程序代码:

use nix::sys::termios;
use std::io::{self, Read, Write};
use std::net::TcpListener;
use std::thread;

fn main() -> io::Result<()> {
    // 保存原始终端属性,退出时恢复
    let orig_termios = termios::tcgetattr(io::stdin())?;
    let mut raw_termios = orig_termios.clone();
    termios::cfmakeraw(&mut raw_termios);
    termios::tcsetattr(io::stdin(), termios::SetArg::TCSANOW, &raw_termios)?;

    // 退出回调,恢复终端属性
    let orig_termios_clone = orig_termios.clone();
    ctrlc::set_handler(move || {
        termios::tcsetattr(io::stdin(), termios::SetArg::TCSANOW, &orig_termios_clone).unwrap();
        std::process::exit(0);
    }).unwrap();

    // 启动监听
    let listener = TcpListener::bind("0.0.0.0:55100")?;
    println!("Started listener");
    let (mut stream, _) = listener.accept()?;
    println!("Connection received");

    // 启动读线程:把远端Shell返回的内容输出到本地终端
    let mut stream_clone = stream.try_clone()?;
    let orig_termios_clone = orig_termios.clone();
    thread::spawn(move || {
        let mut buf = [0; 1024];
        loop {
            let n = stream_clone.read(&mut buf).unwrap();
            if n == 0 {
                println!("\r\nConnection lost");
                termios::tcsetattr(io::stdin(), termios::SetArg::TCSANOW, &orig_termios_clone).unwrap();
                std::process::exit(0x0100);
            }
            io::stdout().write_all(&buf[..n]).unwrap();
            io::stdout().flush().unwrap();
        }
    });

    // 输入状态维护
    let mut input_buf = String::new();
    let mut history: Vec<String> = Vec::new();
    let mut history_idx = 0;

    // 处理本地输入
    let mut stdin = io::stdin();
    let mut key_buf = [0; 4];
    loop {
        let n = stdin.read(&mut key_buf)?;
        match &key_buf[..n] {
            // 回车键
            b"\n" | b"\r" => {
                // 输出换行
                print!("\r\n");
                io::stdout().flush()?;
                // 发送到远端
                stream.write_all(input_buf.as_bytes())?;
                stream.write_all(b"\n")?;
                stream.flush()?;
                // 加入历史,清空缓冲区
                if !input_buf.is_empty() {
                    history.push(input_buf.clone());
                }
                input_buf.clear();
                history_idx = history.len();
            }
            // 退格键
            b"\x7f" | b"\x08" => {
                if !input_buf.is_empty() {
                    input_buf.pop();
                    // 擦除终端上的字符
                    print!("\x08 \x08");
                    io::stdout().flush()?;
                }
            }
            // 上箭头
            b"\x1b[A" => {
                if !history.is_empty() && history_idx > 0 {
                    history_idx -= 1;
                    // 擦除当前行输入
                    for _ in 0..input_buf.len() {
                        print!("\x08 \x08");
                    }
                    // 加载历史命令
                    input_buf = history[history_idx].clone();
                    print!("{}", input_buf);
                    io::stdout().flush()?;
                }
            }
            // 下箭头
            b"\x1b[B" => {
                if history_idx < history.len() {
                    history_idx += 1;
                    // 擦除当前行输入
                    for _ in 0..input_buf.len() {
                        print!("\x08 \x08");
                    }
                    // 加载历史命令,到最新则为空
                    input_buf = if history_idx == history.len() {
                        String::new()
                    } else {
                        history[history_idx].clone()
                    };
                    print!("{}", input_buf);
                    io::stdout().flush()?;
                }
            }
            // 普通可打印字符
            _ => {
                if let Ok(s) = String::from_utf8(key_buf[..n].to_vec()) {
                    input_buf.push_str(&s);
                    print!("{}", s);
                    io::stdout().flush()?;
                }
            }
        }
    }
}

不需要ctrlc依赖的话可以删除对应逻辑,改用自定义信号处理即可,不影响核心功能。

内容的提问来源于stack exchange,提问作者user16139080

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.17 09:06:06