shadow1ng/fscan · error

%s: %w [command_read_failed]

Error message

%s: %w [command_read_failed]

What it means

Reading a command line from the reverse-shell connection failed with a non-timeout error, so the command loop cannot continue. Timeout errors are explicitly filtered (continue); any other read failure is fatal and wrapped here.

Source

Thrown at plugins/local/reverseshell.go:144

		// 发送提示符
		prompt := fmt.Sprintf("%s> ", getCurrentDir())
		_, _ = conn.Write([]byte(prompt))

		// 设置读取超时,以便能响应 ctx 取消
		_ = conn.SetReadDeadline(time.Now().Add(1 * time.Second))

		// 读取命令
		cmdLine, err := reader.ReadString('\n')
		if err != nil {
			if err == io.EOF {
				return nil
			}
			// 超时继续循环检查 ctx
			var netErr net.Error
			if errors.As(err, &netErr) && netErr.Timeout() {
				continue
			}
			return fmt.Errorf("%s: %w", i18n.GetText("command_read_failed"), err)
		}

		// 清理命令
		cmdLine = strings.TrimSpace(cmdLine)
		if cmdLine == "" {
			continue
		}

		// 检查退出命令
		if cmdLine == "exit" {
			_, _ = conn.Write([]byte("Goodbye!\n"))
			return nil
		}

		// 执行命令
		result := p.executeCommand(cmdLine)

		// 发送结果

View on GitHub (pinned to 95cc12e753)

Solutions

  1. Check whether the peer connection was reset (net.OpError / ECONNRESET) and reconnect the reverse shell.
  2. Add periodic keepalive traffic or shorter SetReadDeadline cycles so dead connections are detected as timeouts, not fatal errors.
  3. Treat EOF distinctly from resets: EOF usually means clean peer close — restart the session instead of reporting an error.
  4. Configure TCP keepalives on the conn (conn.(*net.TCPConn).SetKeepAlive(true)).

Example fix

// before
return fmt.Errorf("%s: %w", i18n.GetText("command_read_failed"), err)
// after — handle EOF/reset as session end, not a hard error
if errors.Is(err, io.EOF) {
	return nil // peer closed cleanly
}
return fmt.Errorf("%s: %w", i18n.GetText("command_read_failed"), err)
Defensive patterns

Strategy: try-catch

Validate before calling

// ensure liveness before looping
if tc, ok := conn.(*net.TCPConn); ok {
	_ = tc.SetKeepAlive(true)
	_ = tc.SetKeepAlivePeriod(30 * time.Second)
}

Type guard

func isTimeout(err error) bool {
	var nerr net.Error
	return errors.As(err, &nerr) && nerr.Timeout()
}

Try / catch

_, err := reader.ReadString('\n')
switch {
case errors.Is(err, io.EOF):
	return nil // clean close: restart session
case isTimeout(err):
	continue
default:
	return fmt.Errorf("%s: %w", i18n.GetText("command_read_failed"), err)
}

Prevention

When it happens

Trigger: The read call on the command stream returns an error that is neither EOF-expected shutdown nor a net.Error timeout — e.g. connection reset, broken pipe after the peer dropped, TLS termination.

Common situations: C2 closed the connection mid-session; NAT/firewall dropped the idle connection; peer sent RST after a hung session exceeded idle timeout.

Related errors


AI-assisted analysis of shadow1ng/fscan@95cc12e753 (2026-09-06). Data as JSON: /api/errors/a8ef0c580d0b5ffa. Report an issue: GitHub.