一、概述
上文提到实现IP的探测存活以及tcp扫描的实现,这部分来分析实现本机网卡信息获取,以及维护一张mac地址表以及ip扫描端口状态表,同时实现syn扫描功能。
项目来源:https://github.com/XinRoom/go-portScan/blob/main/util/file.go
二、识别本机网卡信息,获取网卡IP、mac、网关地址等信息
1、/core/syn/device.go
文件代码主要是用于获取网络接口信息,包括查找设备、获取设备信息、获取设备的 MAC 地址以及确定路由器等。
- func GetAllDevs() (string, error)
获取所有的网络设备列表并返回其名称、描述和地址
func GetAllDevs() (string, error) { pcapDevices, err := pcap.FindAllDevs()//获取所有网络设备 if err != nil { return "", errors.New(fmt.Sprintf("list pcapDevices failed: %s", err.Error())) } var buf strings.Builder for _, dev := range pcapDevices { buf.WriteString(fmt.Sprint("Dev:", dev.Name, "\tDes:", dev.Description))//获取网卡名称 if len(dev.Addresses) > 0 { buf.WriteString(fmt.Sprint("\tAddr:", dev.Addresses[0].IP.String()))//获取第一个IP } buf.WriteString("\n") } return buf.String(), nil }
- func GetDevByIp(ip net.IP) (devName string, err error)
根据给定的 IP 地址获取对应的网络设备名称。
// GetDevByIp get dev name by dev ip (use pcap) func GetDevByIp(ip net.IP) (devName string, err error) { devices, err := pcap.FindAllDevs() if err != nil { return } for _, d := range devices {//遍历匹配每个网络设备的IP for _, address := range d.Addresses { _ip := address.IP.To4() if _ip != nil && _ip.IsGlobalUnicast() && _ip.Equal(ip) { return d.Name, nil } } } return "", errors.New("can not find dev") }
- func GetIfaceMac(ifaceAddr net.IP) (src net.IP, mac net.HardwareAddr)
这个函数首先获取系统上所有的网络接口信息。然后,它遍历每个网络接口,检查每个接口的地址列表,以确定是否包含了给定的接口 IP 地址。一旦找到包含指定 IP 的接口,它就会返回该 IP 地址以及对应网络接口的 MAC 地址。如果未找到对应的接口 IP 地址,函数将返回两个 nil 值。
// GetIfaceMac get interface mac addr by interface ip (use golang net) func GetIfaceMac(ifaceAddr net.IP) (src net.IP, mac net.HardwareAddr) { // 获取系统上所有的网络接口信息 interfaces, _ := net.Interfaces() for _, iface := range interfaces { // 获取当前网络接口的地址列表 if addrs, err := iface.Addrs(); err == nil { for _, addr := range addrs { // 检查当前地址是否包含给定的接口 IP 地址 if addr.(*net.IPNet).Contains(ifaceAddr) { // 如果包含,返回对应的 IP 地址和网络接口的 MAC 地址 return addr.(*net.IPNet).IP, iface.HardwareAddr } } } } // 如果未找到对应的接口 IP 地址,则返回 nil return nil, nil }
- func GetMacByGw(gw net.IP) (srcIp net.IP, srcMac net.HardwareAddr, devname string, err error)
这个函数首先使用
GetIfaceMac
函数获取网关对应的源 IP 地址和源 MAC 地址。如果未找到与网关关联的源 IP 地址,函数会返回一个错误信息。接着,它将获取的源 IP 地址转换为 IPv4 格式,并获取系统中的所有网络设备信息。随后,函数遍历设备列表,检查每个设备是否存在地址信息并且与源 IP 地址匹配。如果找到匹配的设备,则将该设备的名称赋值给devname
,并返回。如果未找到匹配的设备,函数会返回另一个错误信息。func GetMacByGw(gw net.IP) (srcIp net.IP, srcMac net.HardwareAddr, devname string, err error) { // 使用 GetIfaceMac 函数获取网关对应的源 IP 地址和源 MAC 地址 srcIp, srcMac = GetIfaceMac(gw) // 如果获取到的源 IP 地址为空,说明无法找到与该网关关联的设备 if srcIp == nil { err = errors.New("can not find this dev by gw") return } // 将源 IP 地址转换为 IPv4 格式 srcIp = srcIp.To4() // 获取系统中的所有网络设备信息 devices, err := pcap.FindAllDevs() if err != nil { return } // 遍历设备列表 for _, d := range devices { // 检查当前设备是否存在地址信息并且与源 IP 地址匹配 if len(d.Addresses) > 0 && d.Addresses[0].IP.String() == srcIp.String() { // 如果匹配成功,将设备名称赋值为当前设备的名称 devname = d.Name return } } // 如果未找到与源 IP 地址匹配的设备,则返回相应的错误信息 err = errors.New("can not find this dev") return }
- func GetRouterV4(dst net.IP) (srcIp net.IP, srcMac net.HardwareAddr, gw net.IP, devName string, err error)
这个函数的作用是根据给定的目标 IP 地址获取路由相关的信息,包括源 IP 地址、源 MAC 地址、网关 IP 地址、设备名称和错误信息。以下是逐行解释:
func GetRouterV4(dst net.IP) (srcIp net.IP, srcMac net.HardwareAddr, gw net.IP, devName string, err error) { // 获取目标 IP 对应的源 IP 和源 MAC 地址 srcIp, srcMac = GetIfaceMac(dst) // 如果源 IP 为空,说明无法找到与目标 IP 相关的设备 if srcIp == nil { // 创建一个路由对象并获取路由表信息 var r routing.Router r, err = netroute.New() if err == nil { // 获取与目标 IP 相关的路由信息 var iface *net.Interface iface, gw, srcIp, err = r.Route(dst) if err == nil { // 如果找到路由,检查对应接口是否存在,如果存在则获取其 MAC 地址,否则再次使用目标 IP 获取源 MAC 地址 if iface != nil { srcMac = iface.HardwareAddr } else { _, srcMac = GetIfaceMac(srcIp) } } } // 如果发生错误或者源 MAC 地址为空,尝试获取默认网关 if err != nil || srcMac == nil { gw, err = gateway.DiscoverGateway() if err == nil { srcIp, srcMac = GetIfaceMac(gw) } } } // 转换网关和源 IP 地址为 IPv4 格式 gw = gw.To4() srcIp = srcIp.To4() // 获取与源 IP 地址相关的设备名称 devName, err = GetDevByIp(srcIp) // 如果源 IP 为空,或者发生错误,或者源 MAC 地址为空,则返回相应的错误信息 if srcIp == nil || err != nil || srcMac == nil { if err == nil { err = fmt.Errorf("err") } return nil, nil, nil, "", fmt.Errorf("no router, %s", err) } // 返回结果 return }
三、维护IP状态表和mac地址表
1、/core/port/syn/watchIpStatus.go
这个代码文件里的结构和方法集合提供了一种有效管理 IP 状态更新的方式,包括记录最后更新时间、检查端口记录以及清理超时数据等功能。
- watchIpStatus结构体
ReceivedPort
是一个map[uint16]struct{}
,用于记录接收到的端口号。LastTime
是一个time.Time
类型的字段,用于记录最后一次更新的时间戳。type watchIpStatus struct { ReceivedPort map[uint16]struct{} LastTime time.Time }
watchIpStatusTable
结构体
watchIpS
: 一个映射表,将 IP 地址与其状态关联起来。lock
: 用于对共享数据进行读写锁操作的sync.RWMutex
实例。isDone
: 标记是否完成清理过期数据的操作。// IP状态更新表 type watchIpStatusTable struct { watchIpS map[string]*watchIpStatus lock sync.RWMutex isDone bool }
- func newWatchIpStatusTable(timeout time.Duration) (w *watchIpStatusTable)
- 创建并返回一个新的
watchIpStatusTable
实例。- 启动一个单独的 goroutine (
go w.cleanTimeout(timeout)
) 来清理过期数据func newWatchIpStatusTable(timeout time.Duration) (w *watchIpStatusTable) { w = &watchIpStatusTable{ watchIpS: make(map[string]*watchIpStatus), } go w.cleanTimeout(timeout) //清理过期数据 return }
- func (w *watchIpStatusTable) UpdateLastTime(ip string)
- 用于更新指定 IP 的最后更新时间。
- 如果 IP 不存在,则新建一个
watchIpStatus
对象,并将其添加到watchIpS
映射表中。// UpdateLastTime 新建或者更新LastTime func (w *watchIpStatusTable) UpdateLastTime(ip string) { lastTime := time.Now() w.lock.Lock() wi, ok := w.watchIpS[ip] if ok { wi.LastTime = lastTime } else {//IP不存在则新建一个映射表 w.watchIpS[ip] = &watchIpStatus{LastTime: lastTime, ReceivedPort: make(map[uint16]struct{})} } w.lock.Unlock() }
- func (w *watchIpStatusTable) RecordPort(ip string, port uint16)
- 记录给定 IP 收到的指定端口信息。
- 如果 IP 存在于映射表中,则将该端口添加到对应 IP 的
ReceivedPort
映射中。// RecordPort 记录收到的端口 func (w *watchIpStatusTable) RecordPort(ip string, port uint16) { w.lock.Lock() wi, ok := w.watchIpS[ip] if ok { wi.ReceivedPort[port] = struct{}{} } w.lock.Unlock() }
- func (w *watchIpStatusTable) HasPort(ip string, port uint16) (has bool)
- 检查给定 IP 是否曾经检测过特定端口。
- 如果 IP 存在于映射表中,并且端口也被记录,则返回
true
,否则返回false
。// HasPort 判断是否检测过对应端口 func (w *watchIpStatusTable) HasPort(ip string, port uint16) (has bool) { w.lock.RLock() wi, ok := w.watchIpS[ip] if ok { _, has = wi.ReceivedPort[port] } w.lock.RUnlock() return }
- func (w *watchIpStatusTable) HasIp(ip string) (has bool)
- 检查是否正在监视给定的 IP。
- 如果 IP 存在于映射表中,则返回
true
,否则返回false
。// HasIp 判断是否在监视对应IP func (w *watchIpStatusTable) HasIp(ip string) (has bool) { w.lock.RLock() _, has = w.watchIpS[ip] w.lock.RUnlock() return }
- func (w *watchIpStatusTable) IsEmpty() (empty bool)
- 检查表是否为空。
- 如果映射表中没有任何 IP 记录,则返回
true
,否则返回false
。// IsEmpty 判断目前表是否为空 func (w *watchIpStatusTable) IsEmpty() (empty bool) { w.lock.RLock() empty = len(w.watchIpS) == 0 w.lock.RUnlock() return }
- func (w *watchIpStatusTable) Close()
设置
isDone
为true
,表示关闭监视表。func (w *watchIpStatusTable) Close() { w.isDone = true }
- func (w *watchIpStatusTable) cleanTimeout(timeout time.Duration)
这个方法
cleanTimeout
是一个后台清理过期数据的功能。它使用一个无限循环来持续检查数据中记录的 IP 地址的最后更新时间,如果超过了设定的超时时间,就会将其删除。这里使用了一个无限循环for {}
来持续检查。// 清理过期数据 func (w *watchIpStatusTable) cleanTimeout(timeout time.Duration) { var needDel map[string]struct{} for { needDel = make(map[string]struct{}) if w.isDone { break } time.Sleep(time.Second) w.lock.RLock() for k, v := range w.watchIpS { if time.Since(v.LastTime) > timeout*time.Millisecond { needDel[k] = struct{}{} } } w.lock.RUnlock() if len(needDel) > 0 { for k := range needDel { w.lock.Lock() delete(w.watchIpS, k) w.lock.Unlock() } } } }
2、/core/port/syn/watchMacCache.go
这个代码文件定义了一个 watchMacCacheTable
结构体,它管理着缓存的 MAC 地址和监听表。其中的方法包括添加、获取和判断是否需要监听某个 IP 地址的 MAC 地址
- watchMacCache结构体
type watchMacCache struct { LastTime time.Time Mac net.HardwareAddr }
- watchMacCacheTable结构体
// Mac缓存和监听表 type watchMacCacheTable struct { watchMacC map[string]*watchMacCache lock sync.RWMutex isDone bool }
- func newWatchMacCacheTable() (w *watchMacCacheTable)
newWatchMacCacheTable()
是用于创建新的watchMacCacheTable
实例的函数。它初始化了一个空的watchMacC
map,这个map用来存储IP地址和对应的缓存信息。同时,这个函数也启动了一个后台任务,定期清理过期的缓存数据func newWatchMacCacheTable() (w *watchMacCacheTable) { w = &watchMacCacheTable{ watchMacC: make(map[string]*watchMacCache), } go w.cleanTimeout() return }
- func (w *watchMacCacheTable) UpdateLastTime(ip string)
UpdateLastTime(ip string)
方法用于更新或添加某个IP地址的最后更新时间。如果这个IP地址已经存在于缓存中,它会更新其最后更新时间;否则,它会创建一个新的缓存项并设置最后更新时间。// UpdateLastTime 新建或者更新LastTime func (w *watchMacCacheTable) UpdateLastTime(ip string) { lastTime := time.Now() w.lock.Lock() wi, ok := w.watchMacC[ip] //如果IP存在,则只更新时间 if ok { wi.LastTime = lastTime } else { w.watchMacC[ip] = &watchMacCache{LastTime: lastTime} } w.lock.Unlock() }
- func (w *watchMacCacheTable) SetMac(ip string, mac net.HardwareAddr)
SetMac(ip string, mac net.HardwareAddr)
方法用于设置某个IP地址对应的MAC地址。它会检查缓存中是否已存在这个IP地址,如果存在则更新其最后更新时间和MAC地址,如果不存在则创建新的缓存项并设置MAC地址。// SetMac 设置Mac地址 func (w *watchMacCacheTable) SetMac(ip string, mac net.HardwareAddr) { lastTime := time.Now() w.lock.Lock() wi, ok := w.watchMacC[ip]//IP存在则更新IP和时间 if ok { wi.LastTime = lastTime wi.Mac = mac } else {//不存在则重新建一个mac映射表 w.watchMacC[ip] = &watchMacCache{LastTime: lastTime, Mac: mac} wi.Mac = mac } w.lock.Unlock() }
- func (w *watchMacCacheTable) GetMac(ip string) (mac net.HardwareAddr)
GetMac(ip string) (mac net.HardwareAddr)
方法用于获取某个IP地址对应的MAC地址。它会根据传入的IP地址在缓存中查找并返回相应的MAC地址。// GetMac 获取Mac地址缓存 func (w *watchMacCacheTable) GetMac(ip string) (mac net.HardwareAddr) { w.lock.RLock() wi, ok := w.watchMacC[ip] if ok { mac = wi.Mac } w.lock.RUnlock() return }
- func (w *watchMacCacheTable) IsNeedWatch(ip string) (has bool)
IsNeedWatch(ip string) (has bool)
方法用于判断是否需要监听某个IP地址的MAC地址。它会检查对应IP地址的MAC地址是否为nil
,如果是nil
,则表示需要监听。// IsNeedWatch 判断是否需要监视 func (w *watchMacCacheTable) IsNeedWatch(ip string) (has bool) { w.lock.RLock() wm, ok := w.watchMacC[ip] has = ok && wm.Mac == nil w.lock.RUnlock() return }
- func (w *watchMacCacheTable) IsEmpty() (empty bool)
IsEmpty() (empty bool)
方法用于判断当前缓存表是否为空// IsEmpty 判断目前表是否为空 func (w *watchMacCacheTable) IsEmpty() (empty bool) { w.lock.RLock() empty = len(w.watchMacC) == 0 w.lock.RUnlock() return }
- func (w *watchMacCacheTable) Close()
Close()
方法用于关闭清理过期数据的后台任务。func (w *watchMacCacheTable) Close() { w.isDone = true }
- func (w *watchMacCacheTable) cleanTimeout()
这个方法是一个后台循环任务,它会定期检查缓存中所有IP地址的最后更新时间。如果某个IP地址的最后更新时间超过了设定的超时时间(这里是10秒),则会将这些过期的IP地址从缓存中删除。这个过程会一直持续,直到
isDone
被设置为true
。// 清理过期数据 func (w *watchMacCacheTable) cleanTimeout() { var needDel map[string]struct{} for { needDel = make(map[string]struct{}) if w.isDone { break } time.Sleep(2 * time.Second) w.lock.RLock() for k, v := range w.watchMacC { if time.Since(v.LastTime) > 10*time.Second { needDel[k] = struct{}{} } } w.lock.RUnlock() if len(needDel) > 0 { for k := range needDel { w.lock.Lock() delete(w.watchMacC, k) w.lock.Unlock() } } } }
四、tcp SYN端口扫描的实现
1、/core/port/syn/syn.go
这段代码是一个TCP SYN扫描器的实现,用于检测主机上开放的TCP端口
- SynScanner 结构体
包含了需要的网络信息和扫描器的配置选项,同时还有一些用于发送和接收数据包的方法。
type SynScanner struct { srcMac, gwMac net.HardwareAddr // macAddr devName string // eth dev(pcap) // gateway (if applicable), and source IP addresses to use. gw, srcIp net.IP // pcap handle *pcap.Handle // opts and buf allow us to easily serialize packets in the send() method. opts gopacket.SerializeOptions // Buffer复用 bufPool *sync.Pool // option port.Option openPortChan chan port.OpenIpPort // inside chan portProbeWg sync.WaitGroup retChan chan port.OpenIpPort // results chan limiter *limiter.Limiter ctx context.Context watchIpStatusT *watchIpStatusTable // IpStatusCacheTable watchMacCacheT *watchMacCacheTable // MacCaches isDone bool }
- func NewSynScanner(firstIp net.IP, retChan chan port.OpenIpPort, option port.Option) (ss *SynScanner, err error)
扫描器的构造函数,用于初始化扫描器并返回一个
SynScanner
实例。它初始化了网络接口、获取了网关信息、创建了pcap
实例用于抓取和发送数据包,设置了相关的过滤器,同时启动了接收数据包的协程。此外,它还创建了用于监视IP状态和MAC缓存的数据结构。// NewSynScanner 用于创建 SynScanner 对象的函数,firstIp: 用于选择路由; openPortChan: 结果返回通道 func NewSynScanner(firstIp net.IP, retChan chan port.OpenIpPort, option port.Option) (ss *SynScanner, err error) { // 选项验证 if option.Rate < 10 { err = errors.New("速率不能小于 10") return } // 设备和网络信息的变量 var devName string var srcIp net.IP var srcMac net.HardwareAddr var gw net.IP // 根据 NextHop 选项指定设备或获取路由器信息 if option.NextHop != "" { // 如果指定了 NextHop,基于该网关 IP 获取信息 gw = net.ParseIP(option.NextHop).To4() srcIp, srcMac, devName, err = GetMacByGw(gw) } else { // 使用提供的第一个 IP 获取路由器信息 srcIp, srcMac, gw, devName, err = GetRouterV4(firstIp) } if err != nil { return } if devName == "" { err = errors.New("获取路由器信息失败:没有设备名称") return } rand.Seed(time.Now().Unix()) // 使用必要的详细信息初始化 SynScanner 对象 ss = &SynScanner{ opts: gopacket.SerializeOptions{ FixLengths: true, ComputeChecksums: true, }, srcIp: srcIp, srcMac: srcMac, devName: devName, bufPool: &sync.Pool{ New: func() interface{} { return gopacket.NewSerializeBuffer() }, }, option: option, openPortChan: make(chan port.OpenIpPort, cap(retChan)), retChan: retChan, limiter: limiter.NewLimiter(limiter.Every(time.Second/time.Duration(option.Rate)), option.Rate/10), ctx: context.Background(), watchIpStatusT: newWatchIpStatusTable(time.Duration(option.Timeout)), watchMacCacheT: newWatchMacCacheTable(), } // 处理不同的扫描模式 if ss.option.FingerPrint || ss.option.Httpx { // 如果启用了指纹识别或 HTTP 探测,启动端口探测处理程序 go ss.portProbeHandle() } else { // 否则,在单独的 goroutine 中处理开放端口通道 go func() { for t := range ss.openPortChan { ss.portProbeWg.Add(1) ss.retChan <- t ss.portProbeWg.Done() } }() } // 为网络包捕获打开 pcap 句柄 handle, err := pcap.OpenLive(devName, 1024, false, pcap.BlockForever) if err != nil { return } // 设置包过滤器以减少监控包的数量 handle.SetBPFFilter(fmt.Sprintf("ether dst %s && (arp || tcp[tcpflags] == tcp-syn|tcp-ack)", srcMac.String())) ss.handle = handle // 开始监听接收到的包 go ss.recv() if gw != nil { // 如果存在网关,则检索其 MAC 地址 var dstMac net.HardwareAddr dstMac, err = ss.getHwAddrV4(gw) if err != nil { return } ss.gwMac = dstMac } return }
- func (ss *SynScanner) Scan(dstIp net.IP, dst uint16) (err error)
Scan
方法用于扫描指定IP和端口。它首先检查IP是否为IPv4地址,然后更新了要监视的IP状态。接着,构建了需要发送的TCP SYN数据包,发送给目标主机。在发送数据包的过程中,会动态调整发送频率,根据待发送队列的占用情况,来控制发送速率。// Scan 用于扫描此扫描器的目标 IP 地址和端口。 func (ss *SynScanner) Scan(dstIp net.IP, dst uint16) (err error) { if ss.isDone { return io.EOF // 如果扫描已完成,则返回 EOF 错误 } // 当队列缓冲区达到一定比例时,调整发送速率 if len(ss.openPortChan)*10 >= cap(ss.openPortChan)*8 { if ss.option.Rate/2 != 0 { ss.limiter.SetLimit(limiter.Every(time.Second / time.Duration(ss.option.Rate/2))) } } else if len(ss.openPortChan)*10 >= cap(ss.openPortChan)*9 { ss.limiter.SetLimit(1) } else { ss.limiter.SetLimit(limiter.Every(time.Second / time.Duration(ss.option.Rate))) } dstIp = dstIp.To4() if dstIp == nil { return errors.New("不是 IPv4 地址") // 如果不是 IPv4 地址,则返回错误 } // 观察 IP,首先更新其时间 ipStr := dstIp.String() ss.watchIpStatusT.UpdateLastTime(ipStr) // 首先获取应该发送数据包的 MAC 地址。 var dstMac net.HardwareAddr if ss.gwMac != nil { dstMac = ss.gwMac } else { // 如果是内网 IP,则从缓存中获取 MAC 地址;否则,获取该 IP 的 MAC 地址 mac := ss.watchMacCacheT.GetMac(ipStr) if mac != nil { dstMac = mac } else { dstMac, err = ss.getHwAddrV4(dstIp) if err != nil { return } } } // 构建所需的所有网络层 eth := layers.Ethernet{ SrcMAC: ss.srcMac, DstMAC: dstMac, EthernetType: layers.EthernetTypeIPv4, } ip4 := layers.IPv4{ SrcIP: ss.srcIp, DstIP: dstIp, Version: 4, TTL: 128, Id: uint16(40000 + rand.Intn(10000)), Flags: layers.IPv4DontFragment, Protocol: layers.IPProtocolTCP, } tcp := layers.TCP{ SrcPort: layers.TCPPort(49000 + rand.Intn(10000)), // 随机源端口,用于确定接收目标端口范围 DstPort: layers.TCPPort(dst), SYN: true, Window: 65280, Seq: uint32(500000 + rand.Intn(10000)), Options: []layers.TCPOption{ { OptionType: layers.TCPOptionKindMSS, OptionLength: 4, OptionData: []byte{0x05, 0x50}, // 1360 }, { OptionType: layers.TCPOptionKindNop, }, { OptionType: layers.TCPOptionKindWindowScale, OptionLength: 3, OptionData: []byte{0x08}, }, { OptionType: layers.TCPOptionKindNop, }, { OptionType: layers.TCPOptionKindNop, }, { OptionType: layers.TCPOptionKindSACKPermitted, OptionLength: 2, }, }, } tcp.SetNetworkLayerForChecksum(&ip4) // 每次循环迭代发送一个数据包,直到发送完毕 ss.send(ð, &ip4, &tcp) return }
- func (ss *SynScanner) Wait()
Wait
方法用于等待扫描器完成所有扫描任务。它首先等待IP状态监视表为空,然后等待扫描结果通道中的数据发送完毕。func (ss *SynScanner) Wait() { // Delay 2s for a reply from the last packet for i := 0; i < 20; i++ { if ss.watchIpStatusT.IsEmpty() { break } time.Sleep(time.Millisecond * 100) } // wait inside chan is empty for len(ss.openPortChan) != 0 { time.Sleep(time.Millisecond * 20) } // wait portProbe task ss.portProbeWg.Wait() }
- func (ss *SynScanner) Close()
Close
方法用于关闭扫描器,清理相关资源,包括关闭pcap
实例、关闭通道,并将SynScanner
结构体标记为已完成状态。// Close 清理处理程序和通道。 func (ss *SynScanner) Close() { ss.isDone = true // 标记扫描完成 if ss.handle != nil { // 在 Linux 下,如果没有数据包要嗅探,pcap 不能使用 BlockForever 停止 // 参考:https://github.com/google/gopacket/issues/890 // 参考:https://github.com/google/gopacket/issues/1089 if runtime.GOOS == "linux" { // 创建一个 ARP 数据包发送以关闭 pcap,使用自身 MAC 地址和 IP 地址 eth := layers.Ethernet{ SrcMAC: ss.srcMac, DstMAC: ss.srcMac, EthernetType: layers.EthernetTypeARP, } arp := layers.ARP{ AddrType: layers.LinkTypeEthernet, Protocol: layers.EthernetTypeIPv4, HwAddressSize: 6, ProtAddressSize: 4, Operation: layers.ARPReply, SourceHwAddress: []byte(ss.srcMac), SourceProtAddress: []byte(ss.srcIp), DstHwAddress: []byte(ss.srcMac), DstProtAddress: []byte(ss.srcIp), } // 打开一个新的 pcap 句柄,发送 ARP 数据包并关闭句柄 handle, _ := pcap.OpenLive(ss.devName, 1024, false, time.Second) buf := ss.bufPool.Get().(gopacket.SerializeBuffer) gopacket.SerializeLayers(buf, ss.opts, ð, &arp) handle.WritePacketData(buf.Bytes()) handle.Close() buf.Clear() ss.bufPool.Put(buf) } ss.handle.Close() // 关闭 pcap 句柄 } // 关闭观察 MAC 地址表和 IP 状态表 if ss.watchMacCacheT != nil { ss.watchMacCacheT.Close() } if ss.watchIpStatusT != nil { ss.watchIpStatusT.Close() } // 清理变量并关闭通道 ss.watchMacCacheT = nil ss.watchIpStatusT = nil close(ss.openPortChan) close(ss.retChan) }
- func (ss *SynScanner) Wait()
WaitLimiter
方法用于等待速率限制,它调用了limiter.Wait()
方法,确保发送速率不超过预设的限制。func (ss *SynScanner) WaitLimiter() error { return ss.limiter.Wait(ss.ctx) }
- func (ss *SynScanner) GetDevName() string
GetDevName
方法用于返回扫描器所选设备的名称// GetDevName Get the device name after the route selection func (ss *SynScanner) GetDevName() string { return ss.devName }
- func (ss *SynScanner) portProbeHandle()
portProbeHandle
方法是一个协程,用于处理端口扫描结果。它不断从openPortChan
通道中获取扫描结果,并针对每个端口进行服务识别和探测。如果启用了服务识别和 HTTP 探测,会调用fingerprint.PortIdentify
和fingerprint.ProbeHttpInfo
方法,识别端口所提供的服务类型和 HTTP 信息,并根据需要更新_openIpPort
的相关信息。最后将结果发送到retChan
通道中。func (ss *SynScanner) portProbeHandle() { for openIpPort := range ss.openPortChan { ss.portProbeWg.Add(1) go func(_openIpPort port.OpenIpPort) { if _openIpPort.Port != 0 { if ss.option.FingerPrint { ss.WaitLimiter() _openIpPort.Service, _openIpPort.Banner, _ = fingerprint.PortIdentify("tcp", _openIpPort.Ip, _openIpPort.Port, 2*time.Second) } if ss.option.Httpx && (_openIpPort.Service == "" || _openIpPort.Service == "http" || _openIpPort.Service == "https") { ss.WaitLimiter() _openIpPort.HttpInfo, _ = fingerprint.ProbeHttpInfo(_openIpPort.Ip, _openIpPort.Port, 2*time.Second) if _openIpPort.HttpInfo != nil { if strings.HasPrefix(_openIpPort.HttpInfo.Url, "https") { _openIpPort.Service = "https" } else { _openIpPort.Service = "http" } } } } ss.retChan <- _openIpPort ss.portProbeWg.Done() }(openIpPort) } }
- func (ss *SynScanner) getHwAddrV4(arpDst net.IP) (mac net.HardwareAddr, err error)
getHwAddrV4
方法用于获取数据包的目标硬件地址,它发送 ARP 请求并等待 ARP 回复以获取目标 IP 的 MAC 地址。在循环中,它会不断发送 ARP 请求,并尝试从缓存中获取 MAC 地址,直到获取到回复或超时为止// getHwAddrV4 获取我们数据包的目标硬件地址。 func (ss *SynScanner) getHwAddrV4(arpDst net.IP) (mac net.HardwareAddr, err error) { ipStr := arpDst.String() // 检查是否需要监视此 IP 的 ARP if ss.watchMacCacheT.IsNeedWatch(ipStr) { return nil, errors.New("此 IP 的 ARP 已在监视中") } ss.watchMacCacheT.UpdateLastTime(ipStr) // 更新 IP 监视时间 // 准备发送 ARP 请求的网络层。 eth := layers.Ethernet{ SrcMAC: ss.srcMac, DstMAC: net.HardwareAddr{0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, // 目标 MAC 地址为广播地址 EthernetType: layers.EthernetTypeARP, } arp := layers.ARP{ AddrType: layers.LinkTypeEthernet, Protocol: layers.EthernetTypeIPv4, HwAddressSize: 6, ProtAddressSize: 4, Operation: layers.ARPRequest, // ARP 请求 SourceHwAddress: []byte(ss.srcMac), SourceProtAddress: []byte(ss.srcIp), DstHwAddress: []byte{0, 0, 0, 0, 0, 0}, // 目标硬件地址为空 DstProtAddress: []byte(arpDst), // 目标 IP 地址 } // 发送 ARP 请求 if err = ss.sendArp(ð, &arp); err != nil { return nil, err } start := time.Now() // 记录开始时间 var retry int // 循环等待获取 ARP 回复 for { mac = ss.watchMacCacheT.GetMac(ipStr) // 获取缓存的 MAC 地址 if mac != nil { return mac, nil // 如果找到了 MAC 地址,返回 } // 等待 600 毫秒获取 ARP 回复 if time.Since(start) > time.Millisecond*600 { return nil, errors.New("获取 ARP 回复超时") } retry += 1 // 每 25 次尝试重新发送 ARP 请求 if retry%25 == 0 { if err = ss.send(ð, &arp); err != nil { return nil, err } } time.Sleep(time.Millisecond * 10) // 休眠 10 毫秒 } }
- func (ss *SynScanner) send(l ...gopacket.SerializableLayer) error
- func (ss *SynScanner) sendArp(l ...gopacket.SerializableLayer) error
send
和sendArp
方法用于发送数据包到网络。send
方法用于发送TCP数据包,而sendArp
方法用于发送ARP请求。注意到在sendArp
方法中,发送的ARP包长度被硬编码为42字节,可能存在需要校正的情况。// send sends the given layers as a single packet on the network. func (ss *SynScanner) send(l ...gopacket.SerializableLayer) error { buf := ss.bufPool.Get().(gopacket.SerializeBuffer) defer func() { buf.Clear() ss.bufPool.Put(buf) }() if err := gopacket.SerializeLayers(buf, ss.opts, l...); err != nil { return err } return ss.handle.WritePacketData(buf.Bytes()) } // send sends the given layers as a single packet on the network., need fix padding func (ss *SynScanner) sendArp(l ...gopacket.SerializableLayer) error { buf := ss.bufPool.Get().(gopacket.SerializeBuffer) defer func() { buf.Clear() ss.bufPool.Put(buf) }() if err := gopacket.SerializeLayers(buf, ss.opts, l...); err != nil { return err } return ss.handle.WritePacketData(buf.Bytes()[:42]) // need fix padding }
- func (ss *SynScanner) recv()
recv
方法负责接收网络上的数据包。它首先解析数据包的各个层级(Ethernet、IPv4、TCP、ARP)并根据协议类型进行处理。对于ARP包,它会解析获取到的IP地址和MAC地址,并更新缓存。对于TCP包,它会匹配源IP和端口,并根据特定条件发送TCP响应包,并将开放端口的信息发送到openPortChan
通道中func (ss *SynScanner) recv() { // 初始化网络层数据 eth := layers.Ethernet{ SrcMAC: ss.srcMac, DstMAC: nil, EthernetType: layers.EthernetTypeIPv4, } ip4 := layers.IPv4{ SrcIP: ss.srcIp, DstIP: []byte{}, // 空的目标 IP 地址 Version: 4, TTL: 64, Protocol: layers.IPProtocolTCP, } tcp := layers.TCP{ SrcPort: 0, DstPort: 0, RST: true, ACK: true, Seq: 1, } // 解码相关的网络层 var ipLayer layers.IPv4 var tcpLayer layers.TCP var arpLayer layers.ARP var ethLayer layers.Ethernet var foundLayerTypes []gopacket.LayerType // 创建一个包解析器用于解析数据包中的各层信息 parser := gopacket.NewDecodingLayerParser( layers.LayerTypeEthernet, ðLayer, &ipLayer, &tcpLayer, &arpLayer, ) // 全局变量 var err error var data []byte var ipStr string var _port uint16 for { // 读取下一个数据包 data, _, err = ss.handle.ReadPacketData() if err != nil { if err == io.EOF { return // 如果出现 EOF,则退出循环 } continue // 继续读取下一个数据包 } // 如果操作已完成,则退出循环 if ss.isDone { return } // 解码 TCP 或 ARP 数据包 err = parser.DecodeLayers(data, &foundLayerTypes) if len(foundLayerTypes) == 0 { continue // 如果未找到任何层信息,继续下一个数据包的解析 } // 解析 ARP 数据包 if arpLayer.SourceProtAddress != nil { ipStr = net.IP(arpLayer.SourceProtAddress).String() if ss.watchMacCacheT.IsNeedWatch(ipStr) { ss.watchMacCacheT.SetMac(ipStr, arpLayer.SourceHwAddress) } arpLayer.SourceProtAddress = nil // 清除 ARP 解析状态 continue } // 匹配 IP 和端口的 TCP 数据包 if tcpLayer.DstPort != 0 && tcpLayer.DstPort >= 49000 && tcpLayer.DstPort <= 59000 { ipStr = ipLayer.SrcIP.String() _port = uint16(tcpLayer.SrcPort) if !ss.watchIpStatusT.HasIp(ipStr) { // 检查 IP continue } else { if ss.watchIpStatusT.HasPort(ipStr, _port) { // 检查端口 continue } else { ss.watchIpStatusT.RecordPort(ipStr, _port) // 记录端口 } } // 如果收到 SYN 和 ACK,则将结果发送到 openPortChan if tcpLayer.SYN && tcpLayer.ACK { ss.openPortChan <- port.OpenIpPort{ Ip: ipLayer.SrcIP, Port: _port, } // 回复目标 eth.DstMAC = ethLayer.SrcMAC ip4.DstIP = ipLayer.SrcIP tcp.DstPort = tcpLayer.SrcPort tcp.SrcPort = tcpLayer.DstPort // RST && ACK tcp.Ack = tcpLayer.Seq + 1 tcp.Seq = tcpLayer.Ack tcp.SetNetworkLayerForChecksum(&ip4) ss.send(ð, &ip4, &tcp) // 发送响应 } tcpLayer.DstPort = 0 // 清除 TCP 解析状态 } } }
2、/core/port/syn/syn_test.go
这份代码中的结构体和方法都是占位符,它们并没有真正的实现功能,而是简单地返回错误或空值。可能是为了暴露接口,并允许使用者在不同情况下自定义更多功能的实现。
type synScanner struct { }
: 定义了一个synScanner
结构体,但它并未实现port.SynScanner
接口。
NewSynScanner
函数: 这是一个函数签名,该函数预期创建一个synScanner
类型的结构体。它接受三个参数:firstIp
用于选择路由,retChan
是结果返回通道,option
是端口扫描的选项配置。然而,在这个实现中,它只是返回一个nil
结构体和一个ErrorNoSyn
错误,意味着没有实际功能被实现。
Scan
、WaitLimiter
、Wait
和Close
方法: 这些方法是synScanner
结构体的方法,但它们只是返回了nil
或空的操作。它们用于实现port.SynScanner
接口,但在这个实现中并未真正执行任何操作。
GetAllDevs
函数: 这个函数用于获取所有设备信息,但类似其他方法,它只是返回一个空字符串和ErrorNoSyn
错误。//go:build nosyn package syn import ( "github.com/XinRoom/go-portScan/core/port" "net" ) type synScanner struct { } // NewSynScanner firstIp: Used to select routes; openPortChan: Result return channel func NewSynScanner(firstIp net.IP, retChan chan port.OpenIpPort, option port.Option) (ss *synScanner, err error) { return nil, ErrorNoSyn } func (ss *synScanner) Scan(dstIp net.IP, dst uint16) error { return nil } func (ss *synScanner) WaitLimiter() error { return nil } func (ss *synScanner) Wait() {} func (ss *synScanner) Close() {} func GetAllDevs() (string, error) { return "", ErrorNoSyn }
3、/core/port/syn/comm.go
这个主要设置syn扫描选项的一些初始值
package syn import ( "errors" "github.com/XinRoom/go-portScan/core/port" ) var ErrorNoSyn = errors.New("no syn support") var DefaultSynOption = port.Option{ Rate: 1500, Timeout: 800, }