分析某款go扫描器之四

一、概述

上文提到实现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.Builderfor _, 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 {//遍历匹配每个网络设备的IPfor _, 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 地址,则返回 nilreturn 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.Namereturn}}// 如果未找到与源 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.Routerr, err = netroute.New()if err == nil {// 获取与目标 IP 相关的路由信息var iface *net.Interfaceiface, 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]*watchIpStatuslock     sync.RWMutexisDone   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) == 0w.lock.RUnlock()return
}

  • func (w *watchIpStatusTable) Close()

设置 isDonetrue,表示关闭监视表。

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.TimeMac      net.HardwareAddr
}
  • watchMacCacheTable结构体
// Mac缓存和监听表
type watchMacCacheTable struct {watchMacC map[string]*watchMacCachelock      sync.RWMutexisDone    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 = lastTimewi.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 == nilw.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) == 0w.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 // macAddrdevName       string           // eth dev(pcap)// gateway (if applicable), and source IP addresses to use.gw, srcIp net.IP// pcaphandle *pcap.Handle// opts and buf allow us to easily serialize packets in the send() method.opts gopacket.SerializeOptions// Buffer复用bufPool *sync.Pool//option         port.OptionopenPortChan   chan port.OpenIpPort // inside chanportProbeWg    sync.WaitGroupretChan        chan port.OpenIpPort // results chanlimiter        *limiter.Limiterctx            context.ContextwatchIpStatusT *watchIpStatusTable // IpStatusCacheTablewatchMacCacheT *watchMacCacheTable // MacCachesisDone         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 stringvar srcIp net.IPvar srcMac net.HardwareAddrvar 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 <- tss.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.HardwareAddrdstMac, 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.HardwareAddrif 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(&eth, &ip4, &tcp)return
}

  • func (ss *SynScanner) Wait() 

Wait 方法用于等待扫描器完成所有扫描任务。它首先等待IP状态监视表为空,然后等待扫描结果通道中的数据发送完毕。

func (ss *SynScanner) Wait() {// Delay 2s for a reply from the last packetfor i := 0; i < 20; i++ {if ss.watchIpStatusT.IsEmpty() {break}time.Sleep(time.Millisecond * 100)}// wait inside chan is emptyfor len(ss.openPortChan) != 0 {time.Sleep(time.Millisecond * 20)}// wait portProbe taskss.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/1089if 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, &eth, &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 = nilss.watchIpStatusT = nilclose(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.PortIdentifyfingerprint.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 <- _openIpPortss.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 的 ARPif 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(&eth, &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(&eth, &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

sendsendArp 方法用于发送数据包到网络。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.IPv4var tcpLayer layers.TCPvar arpLayer layers.ARPvar ethLayer layers.Ethernetvar foundLayerTypes []gopacket.LayerType// 创建一个包解析器用于解析数据包中的各层信息parser := gopacket.NewDecodingLayerParser(layers.LayerTypeEthernet,&ethLayer,&ipLayer,&tcpLayer,&arpLayer,)// 全局变量var err errorvar data []bytevar ipStr stringvar _port uint16for {// 读取下一个数据包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) { // 检查 IPcontinue} else {if ss.watchIpStatusT.HasPort(ipStr, _port) { // 检查端口continue} else {ss.watchIpStatusT.RecordPort(ipStr, _port) // 记录端口}}// 如果收到 SYN 和 ACK,则将结果发送到 openPortChanif tcpLayer.SYN && tcpLayer.ACK {ss.openPortChan <- port.OpenIpPort{Ip:   ipLayer.SrcIP,Port: _port,}// 回复目标eth.DstMAC = ethLayer.SrcMACip4.DstIP = ipLayer.SrcIPtcp.DstPort = tcpLayer.SrcPorttcp.SrcPort = tcpLayer.DstPort// RST && ACKtcp.Ack = tcpLayer.Seq + 1tcp.Seq = tcpLayer.Acktcp.SetNetworkLayerForChecksum(&ip4)ss.send(&eth, &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 错误,意味着没有实际功能被实现。

  • ScanWaitLimiterWaitClose 方法: 这些方法是 synScanner 结构体的方法,但它们只是返回了 nil 或空的操作。它们用于实现 port.SynScanner 接口,但在这个实现中并未真正执行任何操作。

  • GetAllDevs 函数: 这个函数用于获取所有设备信息,但类似其他方法,它只是返回一个空字符串和 ErrorNoSyn 错误。

//go:build nosynpackage synimport ("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 synimport ("errors""github.com/XinRoom/go-portScan/core/port"
)var ErrorNoSyn = errors.New("no syn support")var DefaultSynOption = port.Option{Rate:    1500,Timeout: 800,
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

深入了解Python中staticmethod的使用技巧

更多资料获取 &#x1f4da; 个人网站&#xff1a;ipengtao.com 在Python中&#xff0c;staticmethod是一种用于定义静态方法的装饰器。静态方法是类中的方法&#xff0c;它不依赖于类的实例&#xff0c;也就是说&#xff0c;可以在没有创建类实例的情况下调用它。在本教程中&…

如何实现免费无限流量云同步笔记软件Obsidian?

目录 前言 如何实现免费无限流量云同步笔记软件Obsidian&#xff1f; 一、简介 软件特色演示&#xff1a; 二、使用免费群晖虚拟机搭建群晖Synology Drive服务&#xff0c;实现局域网同步 1 安装并设置Synology Drive套件 2 局域网内同步文件测试 三、内网穿透群晖Synol…

acwing-蓝桥杯C++ AB组辅导课Day2-递归习题+递推+二分

感谢梦翔老哥的蓝桥杯C AB组辅导课~ 递归习题&#xff1a; 1.递归实现组合型枚举 题意&#xff1a; 题目要求输出组合枚举&#xff0c;与排列不同&#xff0c;排列具有顺序之分&#xff0c;对于组合来说&#xff0c;是没有顺序之分的&#xff0c;所以[1,2,3]和[3,2,1]被看成同…

istio 认证:对等身份认证+服务请求认证

istio 中有两种不同的身份认证方式&#xff1a; 基于 mTLS 的对等身份认证 PeerAuthentication基于 JWT&#xff08;JSON Web Token&#xff09;令牌的服务请求认证 RequestAuthentication 对等身份认证 PeerAuthentication 概念 提供服务到服务的认证服务网格的主要场景就…

C# SixLabors.ImageSharp.Drawing的多种用途

生成验证码 /// <summary> /// 生成二维码 /// </summary> /// <param name"webRootPath">wwwroot目录</param> /// <param name"verifyCode">验证码</param> /// <param name"width">图片宽度</…

【06】GeoScene海图或者电子航道图数据自动化质检

1 S-58错误管理器验证产品 在你编辑数据时进行快速的质量检查可以使用S-58错误管理器&#xff0c;S-58错误管理器工具允许您使用IHO S-58验证标准来验证海事数据库中的产品。你可以验证整个产品&#xff0c;或验证产品的当前范围。 1.1验证产品 使用S-58错误管理器工具完成以…

轻松实现 Linux 搭建 KMS 服务器,想做什么就做什么(附所有资料)

轻松实现 Linux 搭建 KMS 服务器,想做什么就做什么(附所有资料)。 支持产品: 下载 vlmcsd 下载文件并解压,把 binaries\Linux\intel\static\ 下的 vlmcsd-x64-musl-static 上传至 VPS/usr/bin/ 目录下,并改名为 vlmcsd。 给予执行权限 chmod +x /usr/bin/vlmcsd开启KM…

微信开发工具修改编译一直报Cannot read property ‘call‘ of undefined?

我个人的解决方法 更新HbuilderX和微信小程序开发者工具到最新版&#xff0c;微信开发者工具-设置-本地设置-调试基础库也换成最新的3.2.4&#xff0c;打开又报错&#xff0c; 把manifest.json文件内的 “mp-weixin” : {“libVersion”: “latest”}配置上就好了 如果不能解…

Axure基础

软件&#xff1a; 简单交互动效 动态面板 显示和隐藏 表单元件 表格设计 内联框架 导航菜单 元件交互样式 滚动屏幕与弹幕

java 4.数组

文章目录 4.数组4.1数组的概念4.2 数组的定义4.3 数组的初始化4.4 数组下标的有效范围与常见异常4.5 数组内存分析4.6 二维数组4.6.1 创建二维数组4.6.2 二维数组的赋值4.6.3 多维数组4.6.4 通过二维数组输出不同版式的古诗 4.7 不规则数组4.8 数组的基本操作4.8.1 数组遍历4.8…

数据结构和算法-平衡二叉树(定义 插入 删除 时间复杂度)

文章目录 平衡二叉树总览平衡二叉树的定义平衡二叉树的插入调整最小不平衡子树在A的左孩子的左子树中插入导致不平衡在A的右孩子的右子树中插入导致不平衡上述两种的代码思路在A的左孩子的右子树中插入导致不平衡在A的右孩子的左子树中插入导致不平衡 填个坑练习查找效率分析小…

锁相放大器(LIA)基本原理

本文介绍锁相放大器(LIA)基本原理。 锁相放大器(LIA)&#xff0c;英文名称&#xff1a;Lock-In Amplifier&#xff0c;在微弱信号检测领域使用非常广泛&#xff0c;比如科研电生理信号测量&#xff0c;传感器信号测量等。本文从理论上分析锁相放大器(LIA)基本原理。 1.基本概…

vivado生成时钟分析

生成的时钟 本节讨论生成的时钟&#xff0c;包括&#xff1a; •关于生成的时钟 •用户定义的生成时钟 •自动衍生时钟 •自动衍生时钟 关于生成的时钟 生成的时钟在设计内部由称为时钟修改块&#xff08;用于例如MMCM&#xff09;&#xff0c;或者通过一些用户逻辑。生…

[JS设计模式]Command Pattern

文章目录 举例说明优点缺点完整代码 With the Command Pattern, we can decouple objects that execute a certain task from the object that calls the method. 使用命令模式&#xff0c;我们可以将执行特定任务的对象与调用该方法的对象解耦。 怎么理解 执行特定任务的对…

基于Java (spring-boot)的课程管理系统

一、项目介绍 ​近年来&#xff0c;随着网络学校规模的逐渐增大&#xff0c;人工书写数据已经不能够处理如此庞大的数据。为了更好的适应信息时代的高效性&#xff0c;一个利用计算机来实现学生信息管理工作的系统将必然诞生。基于这一点&#xff0c;设计了一个学生信息管理系统…

Mybatis基本操作

目录 准备工作 删除操作 预编译SQL 增加操作 获取返回的主键 更新操作 准备工作 准备数据库表 emp创建一个新的springboot工程&#xff0c;选择引入对应的起步依赖&#xff08;mybatis、mysql驱动、lombok&#xff09;application.properties中引入数据库连接信息创建对应…

PSP - 蛋白质与蛋白质的扩散对接 DiffDock-PP 算法

欢迎关注我的CSDN&#xff1a;https://spike.blog.csdn.net/ 本文地址&#xff1a;https://spike.blog.csdn.net/article/details/135115528 DiffDock-PP is a new approach to rigid-body protein-protein docking that is based on a diffusion generative model that learns…

软件工程快速复习(期末急救)

每个同学要假想自己是一个项目经理&#xff0c;去完成一个软件项目&#xff0c;比如医院管理系统&#xff0c;自动设备控制系统等&#xff0c;以面向结构的软件工程方法&#xff0c;说出完成项目的步骤&#xff0c;涉及到的具体技术。初步了解面向对象的方法的与面向结构的方法…

【java】java学习笔记

1. 快速入门 // Hello类 public class Hello {// main方法public static void main(String[] args) {System.out.println("hello world!");} } 在控制台输入以下命令&#xff0c;对.java文件&#xff08;源文件&#xff09;进行编译操作&#xff0c;生成Hello.clas…

每日一题,二维平面

给你 二维 平面上两个 由直线构成且边与坐标轴平行/垂直 的矩形&#xff0c;请你计算并返回两个矩形覆盖的总面积。 每个矩形由其 左下 顶点和 右上 顶点坐标表示&#xff1a; 第一个矩形由其左下顶点 (ax1, ay1) 和右上顶点 (ax2, ay2) 定义。 第二个矩形由其左下顶点 (bx1, …