Go黑帽子(第二章)

2024-01-27 08:20
文章标签 go 第二章 帽子

本文主要是介绍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.Write
          • 2.4.2 创建回显服务器
          • 2.4.3 创建带缓冲的监听器
          • 2.4.4 代理一个TCP客户端

2.1 TCP握手机制

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

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

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

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

单个端口扫描器

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

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

package mainimport ("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 mainimport ("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 mainimport ("fmt""net""sync"
)func main() {var wg sync.WaitGroupfor 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)函数或方法的执行,通常用于确保在函数执行结束时(无论是正常返回还是发生异常)执行一些清理操作。(个人理解就是,只有当函数执行结束之前执行的语句,也就是最后一个执行的语句)

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

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

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

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

package mainimport ("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 mainimport ("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 []intfor 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 portformatimport ("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 mainimport ("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 FooReaderwriter 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 mainimport ("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 FooReaderwriter FooWriter)//这里函数的调用是隐式的,是单个的调用先w->rif _, err := io.Copy(&writer, &reader); err != nil {log.Fatalln("Unable to read/write data")}
}
//简化
2.4.2 创建回显服务器
package mainimport ("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 mainimport ("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 mainimport ("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 mainimport ("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黑帽子(第二章)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!


原文地址:
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.chinasem.cn/article/649633

相关文章

Go 语言中的select语句详解及工作原理

《Go语言中的select语句详解及工作原理》在Go语言中,select语句是用于处理多个通道(channel)操作的一种控制结构,它类似于switch语句,本文给大家介绍Go语言中的select语... 目录Go 语言中的 select 是做什么的基本功能语法工作原理示例示例 1:监听多个通道示例 2:带

Go标准库常见错误分析和解决办法

《Go标准库常见错误分析和解决办法》Go语言的标准库为开发者提供了丰富且高效的工具,涵盖了从网络编程到文件操作等各个方面,然而,标准库虽好,使用不当却可能适得其反,正所谓工欲善其事,必先利其器,本文将... 目录1. 使用了错误的time.Duration2. time.After导致的内存泄漏3. jsO

go中空接口的具体使用

《go中空接口的具体使用》空接口是一种特殊的接口类型,它不包含任何方法,本文主要介绍了go中空接口的具体使用,具有一定的参考价值,感兴趣的可以了解一下... 目录接口-空接口1. 什么是空接口?2. 如何使用空接口?第一,第二,第三,3. 空接口几个要注意的坑坑1:坑2:坑3:接口-空接口1. 什么是空接

利用Go语言开发文件操作工具轻松处理所有文件

《利用Go语言开发文件操作工具轻松处理所有文件》在后端开发中,文件操作是一个非常常见但又容易出错的场景,本文小编要向大家介绍一个强大的Go语言文件操作工具库,它能帮你轻松处理各种文件操作场景... 目录为什么需要这个工具?核心功能详解1. 文件/目录存javascript在性检查2. 批量创建目录3. 文件

Go语言中最便捷的http请求包resty的使用详解

《Go语言中最便捷的http请求包resty的使用详解》go语言虽然自身就有net/http包,但是说实话用起来没那么好用,resty包是go语言中一个非常受欢迎的http请求处理包,下面我们一起来学... 目录安装一、一个简单的get二、带查询参数三、设置请求头、body四、设置表单数据五、处理响应六、超

Golang基于内存的键值存储缓存库go-cache

《Golang基于内存的键值存储缓存库go-cache》go-cache是一个内存中的key:valuestore/cache库,适用于单机应用程序,本文主要介绍了Golang基于内存的键值存储缓存库... 目录文档安装方法示例1示例2使用注意点优点缺点go-cache 和 Redis 缓存对比1)功能特性

Go 1.23中Timer无buffer的实现方式详解

《Go1.23中Timer无buffer的实现方式详解》在Go1.23中,Timer的实现通常是通过time包提供的time.Timer类型来实现的,本文主要介绍了Go1.23中Timer无buff... 目录Timer 的基本实现无缓冲区的实现自定义无缓冲 Timer 实现更复杂的 Timer 实现总结在

Go使用pprof进行CPU,内存和阻塞情况分析

《Go使用pprof进行CPU,内存和阻塞情况分析》Go语言提供了强大的pprof工具,用于分析CPU、内存、Goroutine阻塞等性能问题,帮助开发者优化程序,提高运行效率,下面我们就来深入了解下... 目录1. pprof 介绍2. 快速上手:启用 pprof3. CPU Profiling:分析 C

使用Go语言开发一个命令行文件管理工具

《使用Go语言开发一个命令行文件管理工具》这篇文章主要为大家详细介绍了如何使用Go语言开发一款命令行文件管理工具,支持批量重命名,删除,创建,移动文件,需要的小伙伴可以了解下... 目录一、工具功能一览二、核心代码解析1. 主程序结构2. 批量重命名3. 批量删除4. 创建文件/目录5. 批量移动三、如何安

Go路由注册方法详解

《Go路由注册方法详解》Go语言中,http.NewServeMux()和http.HandleFunc()是两种不同的路由注册方式,前者创建独立的ServeMux实例,适合模块化和分层路由,灵活性高... 目录Go路由注册方法1. 路由注册的方式2. 路由器的独立性3. 灵活性4. 启动服务器的方式5.