2.0 第二章 TCP、扫描器和代理
文章目录
-
-
- [2.0 第二章 TCP、扫描器和代理](#2.0 第二章 TCP、扫描器和代理)
-
- [2.1 TCP握手机制](#2.1 TCP握手机制)
- [2.2 通过端口转发绕过防火墙](#2.2 通过端口转发绕过防火墙)
- [2.3 编写一个TCP扫描器](#2.3 编写一个TCP扫描器)
-
- [2.3.1 测试端口可用性](#2.3.1 测试端口可用性)
- [2.3.2 执行非并发扫描](#2.3.2 执行非并发扫描)
- [2.3.3 执行并发扫描](#2.3.3 执行并发扫描)
- [2.4 构造TCP代理](#2.4 构造TCP代理)
-
- [2.4.1 使用io.Reader 和io.Write](#2.4.1 使用io.Reader 和io.Write)
- [2.4.2 创建回显服务器](#2.4.2 创建回显服务器)
- [2.4.3 创建带缓冲的监听器](#2.4.3 创建带缓冲的监听器)
- [2.4.4 代理一个TCP客户端](#2.4.4 代理一个TCP客户端)
-
2.1 TCP握手机制
这部分内容就不详细说了,百度讲的会更详细,如果看不懂请移步到哔哩哔哩大学。
2.2 通过端口转发绕过防火墙
说白了就是防火墙不允许访问的网站,通过访问允许访问的网站将流量进行代理到目标网站上。
2.3 编写一个TCP扫描器
2.3.1 测试端口可用性
单个端口扫描器
go
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 执行非并发扫描
说白了就是循环检测,看看能不能脸上,我觉得这个效率极低。
go
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
go
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,这是一种控制并发的线程的安全的方法。
修改后的代码如下:
go
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池管理真正进行的并发工作
go
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)
}
综合上面的代码得到:
go
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的作用还有确保指定扫描范围的端口全部完成,防止出现竞态条件。
扫描器解析端口字符串
go
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方法
go
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)
}
这个程序(说白了就是自定义类型实现该接口,然后根据自己的需要进行方法的重写,感觉可以和那个扫描器解析端口字符串
集合起来一起使用,晚点试试,如果结合的较好的话我会发出来供大家一起讨论)应该不难理解,所以我就不详细给大家描述了,如果有问题百度或者留言给我。
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
)
//这里函数的调用是隐式的,是单个的调用先w->r
if _, err := io.Copy(&writer, &reader); err != nil {
log.Fatalln("Unable to read/write data")
}
}
//简化
2.4.2 创建回显服务器
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 != 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)
}
}
给出一个简单的客户端
go
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 创建带缓冲的监听器
go
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)
}
}
客户端
go
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客户端
这个示例我单独重写一个博客