主页 > 软件开发  > 

Go黑帽子(第二章)


2.0 第二章 TCP、扫描器和代理

文章目录 2.0 第二章 TCP、扫描器和代理2.1 TCP握手机制2.2 通过端口转发绕过防火墙2.3 编写一个TCP扫描器2.3.1 测试端口可用性2.3.2 执行非并发扫描2.3.3 执行并发扫描 2.4 构造TCP代理2.4.1 使用io.Reader 和io.Write2.4.2 创建回显服务器2.4.3 创建带缓冲的监听器2.4.4 代理一个TCP客户端

2.1 TCP握手机制

这部分内容就不详细说了,百度讲的会更详细,如果看不懂请移步到哔哩哔哩大学。

2.2 通过端口转发绕过防火墙

说白了就是防火墙不允许访问的网站,通过访问允许访问的网站将流量进行代理到目标网站上。

2.3 编写一个TCP扫描器 2.3.1 测试端口可用性

单个端口扫描器

package main import ( "fmt" "net" ) func main() { _, err := net.Dial("tcp", "scanme.nmap.org:80") if err == nil { fmt.Println("Connection successful") } } 2.3.2 执行非并发扫描

说白了就是循环检测,看看能不能脸上,我觉得这个效率极低。

package main import ( "fmt" "net" ) func main() { for i := 1; i <= 1024; i++ { address := fmt.Sprintf("scanme.nmap.org:%d", i) conn, err := net.Dial("tcp", address) if err == nil { fmt.Println("Connection successful") } else { //端口已关闭或已过滤 continue } conn.Close() fmt.Printf("%d opne\n", i) } } 2.3.3 执行并发扫描

这个时候用到go语言的独有的goroutine

package main import ( "fmt" "net" ) func main() { for i := 1; i <= 1024; i++ { go func(j int) { address := fmt.Sprintf("scanme.nmap.org:%d", j) conn, err := net.Dial("tcp", address) if err != nil { return } conn.Close() fmt.Printf("%d open\n", j) }(i) } } //这里给大家对这个程序进行解释 在这个代码片段中,i 被传递给了匿名函数作为参数 j。这是为了避免在并发的情况下出现竞态条件。 在Go语言中,使用 go 关键字启动一个 goroutine 时,它会在一个新的 goroutine 中执行指定的函数。由于 goroutines 是并发执行的,它们可能会在同一时间访问和修改相同的变量。在这个例子中,i 是在 for 循环中定义的,如果直接在匿名函数中使用 i,会导致竞态条件,因为 i 的值在 goroutines 中可能会被不同的 goroutines 修改。 通过将 i 作为参数传递给匿名函数,确保每个 goroutine 都使用了 for 循环中当前迭代的 i 值的副本,而不是共享相同的 i 变量。这有助于避免竞态条件和确保正确的结果。 所以,i 在后面是为了确保在 go 关键字创建的 goroutine 中使用当前迭代的 i 的正确副本。 竞态条件(Race Condition)是指在多线程或多进程的程序中,由于执行顺序不确定性导致的程序行为异常的情况。竞态条件发生在多个线程或进程同时访问共享资源,并且其中至少一个是写操作时。 竞态条件的发生通常需要满足以下几个条件: 并发访问: 两个或多个线程(或进程)同时访问相同的共享资源。 至少一个写操作: 其中至少有一个线程执行写操作,修改共享资源的状态。 无同步机制: 缺乏适当的同步机制,导致多个线程之间的执行顺序不确定。

以上代码在for循环结束后就会直接退出程序,说明这样做是有问题的,因为for循环完成的时间可能会小于完成连接所需要的时间 ,因此我们需要使用sync包中的waitgroup,这是一种控制并发的线程的安全的方法。

修改后的代码如下:

package main import ( "fmt" "net" "sync" ) func main() { var wg sync.WaitGroup for i := 1; i <= 1024; i++ { wg.Add(1)//递增计数器 go func(j int) { defer wg.Done() address := fmt.Sprintf("scanme.nmap.org:%d", j) conn, err := net.Dial("tcp", address) if err != nil { return } conn.Close() fmt.Printf("%d open\n", j) }(i) } wg.Wait() }

defer 是 Go 语言中的一个关键字,用于延迟(defer)函数或方法的执行,通常用于确保在函数执行结束时(无论是正常返回还是发生异常)执行一些清理操作。(个人理解就是,只有当函数执行结束之前执行的语句,也就是最后一个执行的语句)

这里可以简单了解一下defer和WaitGroup的用法,这里就不做详细解释。

以上代码还存在蛮多的问题比如:并发量太大,可能导致网络或系统限制、网络超时之后的操作等并没有给出。

因此代码可以进行进一步的优化:

使用goroutine池管理真正进行的并发工作

package main import ( "fmt" "sync" ) func worker(ports chan int, wg *sync.WaitGroup) { for p := range ports { fmt.Println(p) wg.Done() } } func main() { ports := make(chan int, 100) var wg sync.WaitGroup //cap测量通道的容量大小 for i := 0; i < cap(ports); i++ { go worker(ports, &wg) } for i := 1; i <= 1024; i++ { wg.Add(1) ports <- i } wg.Wait() close(ports) }

综合上面的代码得到:

package main import ( "fmt" "net" "sort" ) //测试连接并将可以连接的端口号传入results通道中 func worker(ports, results chan int) { for p := range ports { address := fmt.Sprintf("scanme.nmap.org:%d", p) conn, err := net.Dial("tcp", address) if err != nil { results <- 0//区分连接成功和不能连接的端口号 continue } conn.Close() results <- p } } func main() { //说白了就是限制了通道的容量,减少并发的数量 ports := make(chan int, 100) results := make(chan int) var openports []int for i := 0; i < cap(ports); i++ { go worker(ports, results) }//相对于上面的代码并发量直接少了10倍 go func() { for i := 1; i <= 1024; i++ { //达到容量后就会阻塞通道,直到有值被取出 ports <- i } }() for i := 0; i < 1024; i++ { port := <-results //将端口号取出存入切片中,目的是使用排序函数将端口号按顺序打印出来 if port != 0 { openports = append(openports, port) } } close(ports) close(results) sort.Ints(openports)//使打印的端口号递增次序 for _, port := range openports { fmt.Printf("%d open\n", port) } }

results的作用还有确保指定扫描范围的端口全部完成,防止出现竞态条件。

扫描器解析端口字符串

package portformat import ( "errors" "strconv" "strings" ) const ( porterrmsg = "Invalid port specification" ) func dashSplit(sp string, ports *[]int) error { //以-为分隔符进行分割,得到的结构以字符串的形式存入字符串切片中 dp := strings.Split(sp, "-") if len(dp) != 2 { return errors.New(porterrmsg) } //将开始的端口号的形式从字符串转换为整型数 start, err := strconv.Atoi(dp[0]) if err != nil { return errors.New(porterrmsg) } end, err := strconv.Atoi(dp[1]) if err != nil { return errors.New(porterrmsg) } //确保端口范围在有效范围里面 if start > end || start < 1 || end > 65535 { return errors.New(porterrmsg) } for ; start <= end; start++ { *ports = append(*ports, start) } return nil } func convertAndAddPort(p string, ports *[]int) error { i, err := strconv.Atoi(p) if err != nil { return errors.New(porterrmsg) } if i < 1 || i > 65535 { return errors.New(porterrmsg) } *ports = append(*ports, i) return nil } // Parse turns a string of ports separated by '-' or ',' and returns a slice of Ints. func Parse(s string) ([]int, error) { //创建一个空的整数切片,用于存储解析后的端口。 ports := []int{} //如果字符串包含逗号和破折号,表示可能包含多个端口和端口范围。 if strings.Contains(s, ",") && strings.Contains(s, "-") { sp := strings.Split(s, ",") for _, p := range sp { if strings.Contains(p, "-") { if err := dashSplit(p, &ports); err != nil { return ports, err } } else { if err := convertAndAddPort(p, &ports); err != nil { return ports, err } } } } else if strings.Contains(s, ",") { sp := strings.Split(s, ",") for _, p := range sp { convertAndAddPort(p, &ports) } } else if strings.Contains(s, "-") { if err := dashSplit(s, &ports); err != nil { return ports, err } } else { if err := convertAndAddPort(s, &ports); err != nil { return ports, err } } return ports, nil } //我觉得作者这里写的真的好!有可能不懂的地方留言给我,我看到消息给大家解答。

这个Go程序定义了一个名为portformat的包,提供了解析和格式化端口规范的功能。它包含一个名为Parse的函数,该函数接受一个表示端口规范的字符串,并返回一个包含解析后端口的整数切片。

2.4 构造TCP代理

Go语言的Net包基本上涵盖了TCP通信所需要的机制。

2.4.1 使用io.Reader 和io.Write

这里是重写了接口里面的read和write方法

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\n", 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\n", s) }

这个程序(说白了就是自定义类型实现该接口,然后根据自己的需要进行方法的重写,感觉可以和那个扫描器解析端口字符串集合起来一起使用,晚点试试,如果结合的较好的话我会发出来供大家一起讨论)应该不难理解,所以我就不详细给大家描述了,如果有问题百度或者留言给我。

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 ) //这里函数的调用是隐式的,是单个的调用先w->r if _, err := io.Copy(&writer, &reader); err != nil { log.Fatalln("Unable to read/write data") } } //简化 2.4.2 创建回显服务器 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 != nil && err != io.EOF { log.Println("Unexpected error") break } if err == io.EOF && size == 0 { log.Println("Client disconnected") 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) } }

给出一个简单的客户端

package main import ( "bufio" "fmt" "log" "net" "os" ) func main() { // Connect to the server on TCP port 20080. conn, err := net.Dial("tcp", "127.0.0.1:20080") if err != nil { log.Fatalln("Unable to connect to the server") } defer conn.Close() // Create a scanner to read input from the user. scanner := bufio.NewScanner(os.Stdin) // Create a goroutine to read and display server responses. go func() { for { // Read server response. response := make([]byte, 512) size, err := conn.Read(response) if err != nil { log.Fatalln("Error reading from server:", err) return } fmt.Printf("Server says: %s\n", string(response[:size])) } }() // Read user input and send it to the server. for { fmt.Print("Enter text to send to the server: ") scanner.Scan() text := scanner.Text() // Send user input to the server. _, err := conn.Write([]byte(text)) if err != nil { log.Fatalln("Error writing to server:", err) return } } }

跑完你会发现,你输入的字符串和在服务端接收到的字符串会不匹配,初步猜测缓冲区的内容没有及时写出

2.4.3 创建带缓冲的监听器 package main import ( "bufio" "log" "net" ) func echo(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() } 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) } }

客户端

package main import ( "bufio" "fmt" "log" "net" "os" ) func main() { // Connect to the server on TCP port 20080. conn, err := net.Dial("tcp", "127.0.0.1:20080") if err != nil { log.Fatalln("Unable to connect to the server") } defer conn.Close() // Create a scanner to read input from the user. scanner := bufio.NewScanner(os.Stdin) // Create a goroutine to read and display server responses. go func() { for { // Read server response. response, err := bufio.NewReader(conn).ReadString('\n') if err != nil { log.Fatalln("Error reading from server:", err) return } fmt.Printf("Server says: %s", response) } }() // Read user input and send it to the server. for { fmt.Print("Enter text to send to the server: ") scanner.Scan() text := scanner.Text() // Send user input to the server. //Fprintln使用其操作数的默认格式进行格式化,并写入w _, err := fmt.Fprintln(conn, text) if err != nil { log.Fatalln("Error writing to server:", err) return } } }

对比2.4.2 和2.4.3 的客户端,两个客户端不能交换使用,因为服务端和客户端对conn的写入和读取方式不一样,建议统一,要么使用缓冲区,要么使用切片,不然会照成无法正常对数据读写的情况。

2.4.4 代理一个TCP客户端

这个示例我单独重写一个博客

标签:

Go黑帽子(第二章)由讯客互联软件开发栏目发布,感谢您对讯客互联的认可,以及对我们原创作品以及文章的青睐,非常欢迎各位朋友分享到个人网站或者朋友圈,但转载请说明文章出处“Go黑帽子(第二章)