一、概述
前兩篇主要分析些工具集,已經(jīng)針對web服務(wù)的指紋和端口指紋信息進行識別,并沒有真正開始掃描。本篇主要分析如何進行IP存活探測以及tcp掃描實現(xiàn)。
項目來源:https://github.com/XinRoom/go-portScan/blob/main/util/file.go
二、/core/host/ 目錄(進行主機的ping探測存活)
/core/host/ping.go
此代碼主要是用來進行主機存活的。
首先設(shè)置判斷變量和常見端口列表
var CanIcmp bool // 用于標(biāo)識是否支持發(fā)送 ICMP 包。
var TcpPingPorts = []uint16{80, 22, 445, 23, 443, 81, 161, 3389, 8080, 8081}//是用于 TCP Ping 的默認(rèn)常見端口列表。
函數(shù)列表分析:
- func IcmpOK(host string) bool?
函數(shù)嘗試直接發(fā)送 ICMP 包來檢查主機是否存活。
// IcmpOK 直接發(fā)ICMP包 func IcmpOK(host string) bool { pinger, err := ping.NewPinger(host) if err != nil { return false } pinger.SetPrivileged(true) pinger.Count = 1 pinger.Timeout = 800 * time.Millisecond if pinger.Run() != nil { // Blocks until finished. return err return false } if stats := pinger.Statistics(); stats.PacketsRecv > 0 { return true } return false }
- func PingOk(host string) bool
函數(shù)嘗試通過執(zhí)行不同操作系統(tǒng)的 Ping 命令來檢查主機是否存活。
// PingOk Ping命令模式 func PingOk(host string) bool { switch runtime.GOOS { case "linux": cmd := exec.Command("ping", "-c", "1", "-W", "1", host) var out bytes.Buffer cmd.Stdout = &out cmd.Run() if strings.Contains(out.String(), "ttl=") { return true } case "windows": cmd := exec.Command("ping", "-n", "1", "-w", "500", host) var out bytes.Buffer cmd.Stdout = &out cmd.Run() if strings.Contains(out.String(), "TTL=") { return true } case "darwin": cmd := exec.Command("ping", "-c", "1", "-t", "1", host) var out bytes.Buffer cmd.Stdout = &out cmd.Run() if strings.Contains(out.String(), "ttl=") { return true } } return false }
- func TcpPing(host string, ports []uint16, timeout time.Duration) (ok bool)
函數(shù)使用 TCP 連接在指定端口上對主機進行存活探測,在連接成功或者連接被拒絕時,都會判斷主機為存活狀態(tài)。
func TcpPing(host string, ports []uint16, timeout time.Duration) (ok bool) { var wg sync.WaitGroup // 創(chuàng)建一個 WaitGroup 用于等待所有端口的探測完成 ctx, cancel := context.WithCancel(context.Background()) // 創(chuàng)建一個上下文和取消函數(shù) d := net.Dialer{ // 創(chuàng)建一個 Dialer,用于建立 TCP 連接 Timeout: timeout + time.Second, // 設(shè)置連接超時時間 KeepAlive: 0, // 禁用 KeepAlive } for _, port := range ports { // 遍歷端口列表 time.Sleep(10 * time.Millisecond) // 間隔一段時間再探測下一個端口,避免過于頻繁的連接 wg.Add(1) // 每個端口探測前增加 WaitGroup 計數(shù) go func(_port uint16) { // 并發(fā)進行端口探測 conn, err := d.DialContext(ctx, "tcp", fmt.Sprintf("%s:%d", host, _port)) // 嘗試建立 TCP 連接 if conn != nil { // 如果成功建立連接 conn.Close() // 關(guān)閉連接 ok = true // 設(shè)置存活狀態(tài)為 true } else if err != nil && strings.Contains(err.Error(), "refused it") { // 如果連接被拒絕 ok = true // 設(shè)置存活狀態(tài)為 true } if ok { // 如果已經(jīng)確認(rèn)存活 cancel() // 取消其他端口的探測 } wg.Done() // 完成當(dāng)前端口的探測 }(_port) // 傳入端口號進行端口探測 } wg.Wait() // 等待所有端口的探測完成 return // 返回存活狀態(tài) }
- func init()
函數(shù)在包加載時運行,它嘗試在本地(127.0.0.1)發(fā)送 ICMP 包來檢查系統(tǒng)是否支持 ICMP。
func init() { if IcmpOK("127.0.0.1") { CanIcmp = true } }
- func IsLive(ip string, tcpPing bool, tcpTimeout time.Duration) (ok bool)
IsLive
函數(shù)是用來檢查主機是否存活的主要函數(shù)。如果支持 ICMP,則使用IcmpOK
函數(shù);否則,使用PingOk
函數(shù)。如果前面的檢查未能確定主機存活且tcpPing
參數(shù)為真,則嘗試使用TcpPing
函數(shù)進行 TCP 存活探測。func IsLive(ip string, tcpPing bool, tcpTimeout time.Duration) (ok bool) { if CanIcmp { ok = IcmpOK(ip) } else { ok = PingOk(ip) } if !ok && tcpPing { ok = TcpPing(ip, TcpPingPorts, tcpTimeout) } return }
ps:總體而言,這些函數(shù)提供了多種方法來檢查主機是否存活,包括使用 ICMP 包、執(zhí)行系統(tǒng)命令(如 Ping 命令)以及 TCP 連接到常見端口。這樣做可以在不同系統(tǒng)和網(wǎng)絡(luò)環(huán)境下更全面地檢查主機的存活狀態(tài)
三、port端口解析,以及tcp掃描
1、/core/port/port.go
這個文件代碼主要包含了端口掃描相關(guān)的邏輯和數(shù)據(jù)結(jié)構(gòu)。
- var TopTcpPorts = []uint16{} 常見端口列表
- 各結(jié)構(gòu)體列表
type Scanner interface {//這是一個接口,定義了掃描器的操作,包括關(guān)閉、等待、掃描指定 IP 和端口,以及等待速率限制器。
Close()
Wait()
Scan(ip net.IP, dst uint16) error
WaitLimiter() error
}
// OpenIpPort retChan,這個結(jié)構(gòu)體表示一個開放的 IP 和端口,包含 IP 地址、端口號、服務(wù)名和可能的 HTTP 信息。
type OpenIpPort struct {
Ip net.IP
Port uint16
Service string
HttpInfo *HttpInfo
}
// Option ...這個結(jié)構(gòu)體包含了掃描的參數(shù)配置,如速率限制、超時時間、網(wǎng)卡信息和是否進行服務(wù)探測等。
type Option struct {
Rate int // 每秒速度限制, 單位: s, 會在1s內(nèi)平均發(fā)送, 相當(dāng)于每個包之間的延遲
Timeout int // TCP連接響應(yīng)延遲, 單位: ms
NextHop string // pcap dev name
FingerPrint bool // 服務(wù)探測
Httpx bool // HttpInfo 探測
}
// HttpInfo Http服務(wù)基礎(chǔ)信息,這個結(jié)構(gòu)體存儲了 HTTP 服務(wù)的基礎(chǔ)信息,包括狀態(tài)碼、響應(yīng)包大小、URL、重定向路徑、標(biāo)題、服務(wù)名、TLS 信息和 Web 指紋
type HttpInfo struct {
StatusCode int // 狀態(tài)碼
ContentLen int // 相應(yīng)包大小
Url string // Url
Location string // 302、301重定向路徑
Title string // 標(biāo)題
Server string // 服務(wù)名
TlsCN string // tls使用者名稱
TlsDNS []string // tlsDNS列表
Fingers []string // 識別到的web指紋
}
- func (op OpenIpPort) String() string
這個函數(shù)是為了實現(xiàn)
Stringer
接口,這個接口包含一個String()
方法,用于定義該類型的字符串表示形式。對于OpenIpPort
類型的實例,這個函數(shù)會返回其 IP 地址、端口號以及可能的服務(wù)信息的字符串表示形式func (op OpenIpPort) String() string { buf := strings.Builder{} buf.WriteString(op.Ip.String()) buf.WriteString(":") buf.WriteString(strconv.Itoa(int(op.Port))) if op.Service != "" { buf.WriteString(" ") buf.WriteString(op.Service) } if op.HttpInfo != nil { buf.WriteString("\n") buf.WriteString(op.HttpInfo.String()) } return buf.String() }
- func (hi *HttpInfo) String() string
這個函數(shù)也是實現(xiàn)
Stringer
接口的方法。對于HttpInfo
類型的實例,這個函數(shù)返回其包含的 HTTP 信息的字符串表示形式,包括狀態(tài)碼、響應(yīng)包大小、URL、重定向路徑、標(biāo)題、服務(wù)名和 Web 指紋等。func (hi *HttpInfo) String() string { if hi == nil { return "" } var buf strings.Builder buf.WriteString(fmt.Sprintf("[HttpInfo]%s StatusCode:%d ContentLen:%d Title:%s ", hi.Url, hi.StatusCode, hi.ContentLen, hi.Title)) if hi.Location != "" { buf.WriteString("Location:" + hi.Location + " ") } if hi.TlsCN != "" { buf.WriteString("TlsCN:" + hi.TlsCN + " ") } if len(hi.TlsDNS) > 0 { buf.WriteString("TlsDNS:" + strings.Join(hi.TlsDNS, ",") + " ") } if hi.Server != "" { buf.WriteString("Server:" + hi.Server + " ") } if len(hi.Fingers) != 0 { buf.WriteString(fmt.Sprintf("Fingers:%s ", hi.Fingers)) } return buf.String() }
- func ParsePortRangeStr(portStr string) (out [][]uint16, err error)
這個函數(shù)用于解析端口字符串,將其轉(zhuǎn)換為端口范圍的列表。
// ParsePortRangeStr 解析端口字符串 func ParsePortRangeStr(portStr string) (out [][]uint16, err error) { portsStrGroup := strings.Split(portStr, ",")//逗號分隔的端口 var portsStrGroup3 []string var portStart, portEnd uint64 for _, portsStrGroup2 := range portsStrGroup { if portsStrGroup2 == "top1000" { continue } portsStrGroup3 = strings.Split(portsStrGroup2, "-")//-作為端口范圍的 portStart, err = strconv.ParseUint(portsStrGroup3[0], 10, 16)//返回端口的整數(shù)值,10進制,類型為int16 if err != nil { return } portEnd = portStart if len(portsStrGroup3) == 2 { portEnd, err = strconv.ParseUint(portsStrGroup3[1], 10, 16) } if err != nil { return } out = append(out, []uint16{uint16(portStart), uint16(portEnd)}) } return }
- func IsInPortRange(port uint16, portRanges [][]uint16) bool
這個函數(shù)用于檢查指定端口是否在端口范圍內(nèi)
// IsInPortRange 判斷port是否在端口范圍里 func IsInPortRange(port uint16, portRanges [][]uint16) bool { for _, portRange := range portRanges { if port >= portRange[0] && port <= portRange[1] { return true } } return false }
- func ShuffleParseAndMergeTopPorts(portStr string) (ports []uint16, err error)
這個函數(shù)主要實現(xiàn)了對端口的解析、合并和隨機化處理。它會解析傳入的端口字符串,根據(jù)配置信息選取一些端口,優(yōu)先使用常見的 TCP 端口,然后從用戶指定的端口范圍中選擇未被選取的端口,并最終隨機排序這些端口
// ShuffleParseAndMergeTopPorts shuffle parse portStr and merge TopTcpPorts func ShuffleParseAndMergeTopPorts(portStr string) (ports []uint16, err error) { if portStr == "" { ports = TopTcpPorts //未指定則用默認(rèn)top端口 return } var portRanges [][]uint16 portRanges, err = ParsePortRangeStr(portStr) if err != nil { return } // 優(yōu)先發(fā)送top端口 selectTopPort := make(map[uint16]struct{}) // TopPort hasTopStr := strings.Contains(portStr, "top1000") for _, _port := range TopTcpPorts { if hasTopStr || IsInPortRange(_port, portRanges) { //檢測端口是否在范圍內(nèi) selectTopPort[_port] = struct{}{} ports = append(ports, _port) } } selectPort := make(map[uint16]struct{}) // OtherPort for _, portRange := range portRanges { var ok bool for _port := portRange[0]; _port <= portRange[1]; _port++ { if _port == 0 { continue } if _, ok = selectTopPort[_port]; ok { continue } else if _, ok = selectPort[_port]; ok { continue } selectPort[_port] = struct{}{} ports = append(ports, _port) //得到所有端口,并將top端口排在前面 if _port == 65535 { break } } } if len(ports) == 0 { err = errors.New("ports len is 0") return } // 端口隨機化 skip := uint64(len(selectTopPort)) // 跳過Top _ports := make([]uint16, len(ports)) copy(_ports, ports) sf := util.NewShuffle(uint64(len(ports)) - skip) if sf != nil { for i := skip; i < uint64(len(_ports)); i++ { ports[i] = _ports[skip+sf.Get(i-skip)] } } return }
ps:這些函數(shù)主要提供了對端口進行解析、篩選和隨機化的功能,以便用于端口掃描和服務(wù)探測。它將常見的 TCP 端口列表與用戶輸入的端口范圍合并,隨機化排序以減少掃描的可預(yù)測性。
2、/core/port/tcp/tcp.go
整體來說,這個代碼文件定義了一個 TCP 端口掃描器,可以根據(jù)指定的 IP 地址和端口號對目標(biāo)進行掃描,并可選地進行服務(wù)探測和 HTTP 信息探測。同時,它實現(xiàn)了速率限制以及 goroutine 的管理,確保掃描操作的安全性和效率。
var DefaultTcpOption = port.Option{//這里定義了默認(rèn)的 TCP 掃描選項,包括掃描速率和超時時間等。
Rate: 1000,
Timeout: 800,
}
type TcpScanner struct {//管理 TCP 端口掃描器的狀態(tài)和操作。結(jié)構(gòu)體中包含了需要的字段和方法。
ports []uint16 // 指定端口
retChan chan port.OpenIpPort // 返回值隊列
limiter *limiter.Limiter
ctx context.Context
timeout time.Duration
isDone bool
option port.Option
wg sync.WaitGroup
}
- func NewTcpScanner(retChan chan port.OpenIpPort, option port.Option) (ts *TcpScanner, err error)
這個函數(shù)是一個構(gòu)造器,用于創(chuàng)建一個新的 TCP 掃描器實例。它接收一個返回值通道
retChan
和掃描選項option
,并返回一個TcpScanner
的指針。函數(shù)首先對傳入的選項進行驗證,確保速率大于等于 10,超時時間大于 0。然后,它初始化了一個TcpScanner
結(jié)構(gòu)體實例,設(shè)置了返回通道、速率限制器、上下文、超時時間和其他選項,并將該實例賦值給ts
,最后返回該實例和可能的錯誤。// NewTcpScanner Tcp掃描器 func NewTcpScanner(retChan chan port.OpenIpPort, option port.Option) (ts *TcpScanner, err error) { // option verify if option.Rate < 10 { err = errors.New("rate can not be set less than 10") // 如果速率小于 10,則返回錯誤 return } if option.Timeout <= 0 { err = errors.New("timeout can not be set to 0") // 如果超時時間小于等于 0,則返回錯誤 return } // 初始化 TcpScanner 結(jié)構(gòu)體 ts = &TcpScanner{ retChan: retChan, // 設(shè)置返回通道 limiter: limiter.NewLimiter(limiter.Every(time.Second/time.Duration(option.Rate)), option.Rate/10), // 設(shè)置速率限制器 ctx: context.Background(), // 初始化上下文 timeout: time.Duration(option.Timeout) * time.Millisecond, // 設(shè)置超時時間 option: option, // 設(shè)置選項 } return // 返回 TcpScanner 實例和可能的錯誤 }
- func (ts *TcpScanner) Scan(ip net.IP, dst uint16) error
這個函數(shù)用于執(zhí)行對指定 IP 和目標(biāo)端口進行掃描的操作。它會啟動一個 goroutine,在其中進行端口掃描并將結(jié)果發(fā)送到
retChan
通道中。函數(shù)首先檢查掃描器是否已關(guān)閉,然后將一個任務(wù)添加到等待組wg
中。接著,它初始化了一個port.OpenIpPort
結(jié)構(gòu)體實例,表示正在掃描的 IP 和端口。接下來的部分涉及服務(wù)指紋識別和 HTTP 信息探測的邏輯。如果設(shè)置了相應(yīng)的選項,它將調(diào)用相關(guān)的函數(shù)進行識別并填充openIpPort
結(jié)構(gòu)體中的信息。最后,如果沒有進行服務(wù)指紋識別或者 HTTP 信息探測,它將嘗試通過net.DialTimeout
進行連接,判斷端口是否開放,并將結(jié)果發(fā)送到通道中。// Scan 對指定IP和dis port進行掃描 func (ts *TcpScanner) Scan(ip net.IP, dst uint16) error { if ts.isDone { return errors.New("scanner is closed") // 如果掃描器已關(guān)閉,則返回錯誤 } ts.wg.Add(1) // 增加等待組中的任務(wù)數(shù) go func() { defer ts.wg.Done() // 標(biāo)記任務(wù)結(jié)束 //fmt.Println(1) openIpPort := port.OpenIpPort{ Ip: ip, Port: dst, } var isDailErr bool if ts.option.FingerPrint { openIpPort.Service, isDailErr = fingerprint.PortIdentify("tcp", ip, dst, 2*time.Second) // 進行服務(wù)指紋識別 if isDailErr { return // 如果識別過程出錯,直接返回 } } if ts.option.Httpx && (openIpPort.Service == "" || openIpPort.Service == "http" || openIpPort.Service == "https") { openIpPort.HttpInfo, isDailErr = fingerprint.ProbeHttpInfo(ip, dst, 2*time.Second) // 進行 HTTP 信息探測 if isDailErr { return // 如果探測過程出錯,直接返回 } if openIpPort.HttpInfo != nil { if strings.HasPrefix(openIpPort.HttpInfo.Url, "https") { openIpPort.Service = "https" // 如果是 HTTPS,則標(biāo)記為 HTTPS 服務(wù) } else { openIpPort.Service = "http" // 如果是 HTTP,則標(biāo)記為 HTTP 服務(wù) } } } if !ts.option.FingerPrint && !ts.option.Httpx { conn, _ := net.DialTimeout("tcp", fmt.Sprintf("%s:%d", ip, dst), ts.timeout) // 嘗試連接端口 if conn != nil { conn.Close() // 如果連接成功,則關(guān)閉連接 } else { return // 如果連接失敗,直接返回 } } ts.retChan <- openIpPort // 將掃描結(jié)果發(fā)送到通道 }() return nil }
- 輔助函數(shù)
func (ts *TcpScanner) Wait()
func (ts *TcpScanner) Close()文章來源:http://www.zghlxwxcb.cn/news/detail-758028.html
func (ts *TcpScanner) WaitLimiter() error文章來源地址http://www.zghlxwxcb.cn/news/detail-758028.html
//這個方法用于等待所有啟動的 goroutine 完成掃描操作。它會等待 wg 等待組中的所有 goroutine 完成。 func (ts *TcpScanner) Wait() { ts.wg.Wait() } // Close chan這個方法用于關(guān)閉 retChan 通道,表示掃描已經(jīng)完成。它還會設(shè)置 isDone 標(biāo)志,表示掃描器已關(guān)閉 func (ts *TcpScanner) Close() { ts.isDone = true close(ts.retChan) } // WaitLimiter Waiting for the speed limit這個方法用于等待速率限制器。它會通過 limiter 控制掃描的速率,以確保按照設(shè)定的速率發(fā)送掃描請求 func (ts *TcpScanner) WaitLimiter() error { return ts.limiter.Wait(ts.ctx) }
到了這里,關(guān)于分析某款go端口掃描器之三的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!