上文提到实现IP的探测存活以及tcp扫描的实现,这部分来分析实现本机网卡信息获取,以及维护一张mac地址表以及ip扫描端口状态表,同时实现syn扫描功能。
项目来源:https://github.com/XinRoom/go-portScan/blob/main/util/file.go
文件代码主要是用于获取网络接口信息,包括查找设备、获取设备信息、获取设备的 MAC 地址以及确定路由器等。
获取所有的网络设备列表并返回其名称、描述和地址
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 }
根据给定的 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") }
这个函数首先获取系统上所有的网络接口信息。然后,它遍历每个网络接口,检查每个接口的地址列表,以确定是否包含了给定的接口 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 }
这个函数首先使用
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 }
这个函数的作用是根据给定的目标 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 状态更新的方式,包括记录最后更新时间、检查端口记录以及清理超时数据等功能。
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 }
- 创建并返回一个新的
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 }
- 用于更新指定 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() }
- 记录给定 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() }
- 检查给定 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 }
- 检查是否正在监视给定的 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 }
- 检查表是否为空。
- 如果映射表中没有任何 IP 记录,则返回
true
,否则返回false
。// IsEmpty 判断目前表是否为空 func (w *watchIpStatusTable) IsEmpty() (empty bool) { w.lock.RLock() empty = len(w.watchIpS) == 0 w.lock.RUnlock() return }
设置
isDone
为true
,表示关闭监视表。func (w *watchIpStatusTable) Close() { w.isDone = true }
这个方法
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() } } } }
这个代码文件定义了一个 watchMacCacheTable
结构体,它管理着缓存的 MAC 地址和监听表。其中的方法包括添加、获取和判断是否需要监听某个 IP 地址的 MAC 地址
type watchMacCache struct { LastTime time.Time Mac net.HardwareAddr }
// Mac缓存和监听表 type watchMacCacheTable struct { watchMacC map[string]*watchMacCache lock sync.RWMutex isDone bool }
newWatchMacCacheTable()
是用于创建新的watchMacCacheTable
实例的函数。它初始化了一个空的watchMacC
map,这个map用来存储IP地址和对应的缓存信息。同时,这个函数也启动了一个后台任务,定期清理过期的缓存数据func newWatchMacCacheTable() (w *watchMacCacheTable) { w = &watchMacCacheTable{ watchMacC: make(map[string]*watchMacCache), } go w.cleanTimeout() return }
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() }
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() }
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 }
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 }
IsEmpty() (empty bool)
方法用于判断当前缓存表是否为空// IsEmpty 判断目前表是否为空 func (w *watchMacCacheTable) IsEmpty() (empty bool) { w.lock.RLock() empty = len(w.watchMacC) == 0 w.lock.RUnlock() return }
Close()
方法用于关闭清理过期数据的后台任务。func (w *watchMacCacheTable) Close() { w.isDone = true }
这个方法是一个后台循环任务,它会定期检查缓存中所有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扫描器的实现,用于检测主机上开放的TCP端口
包含了需要的网络信息和扫描器的配置选项,同时还有一些用于发送和接收数据包的方法。
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 }
扫描器的构造函数,用于初始化扫描器并返回一个
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 }
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 }
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() }
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) }
WaitLimiter
方法用于等待速率限制,它调用了limiter.Wait()
方法,确保发送速率不超过预设的限制。func (ss *SynScanner) WaitLimiter() error { return ss.limiter.Wait(ss.ctx) }
GetDevName
方法用于返回扫描器所选设备的名称// GetDevName Get the device name after the route selection func (ss *SynScanner) GetDevName() string { return ss.devName }
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) } }
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 毫秒 } }
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 }
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 解析状态 } } }
这份代码中的结构体和方法都是占位符,它们并没有真正的实现功能,而是简单地返回错误或空值。可能是为了暴露接口,并允许使用者在不同情况下自定义更多功能的实现。
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 }
这个主要设置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, }