分析某款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.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()

设置 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.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(&eth, &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, &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 = 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.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 <- _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(&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.IPv4
	var tcpLayer layers.TCP
	var arpLayer layers.ARP
	var ethLayer layers.Ethernet
	var foundLayerTypes []gopacket.LayerType

	// 创建一个包解析器用于解析数据包中的各层信息
	parser := gopacket.NewDecodingLayerParser(
		layers.LayerTypeEthernet,
		&ethLayer,
		&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(&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 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,
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

PY32F072片内闪存读写 HAL库

我这个hal库底层是一次写一页&#xff0c;擦除也是以页为单位的。这个芯片太偏了&#xff0c;有点小恶心。 flash.c uint8_t flash_read(uint32_t add) {uint8_t temp;temp *(__IO uint8_t *)(add);return temp; }void flash_read_buf(uint32_t add, uint8_t *data, uint8_t…

【ICCV 2023】MPI-Flow:什么,只需要单张图片就能训练光流估计模型了?

ICCV 2023 | MPI-Flow&#xff1a;从单视角构建的多平面图像中学习光流 引言&#xff1a;主要贡献&#xff1a;Motivation&#xff1a;算法细节&#xff1a;Optical Flow Data GenerationIndependent Object MotionsDepth-Aware Inpainting 实验结果&#xff1a; 来源&#xff…

Python实现多元线性回归模型信用卡客户价值预测项目源码+数据+项目设计报告

多元线性回归——信用卡客户价值预测 一、背景 这里以信用卡客户的客户价值为例来解释客户价值预测的具体含义&#xff1a; 客户价值预测就是指预测客户在未来一段时间内能带来多少利润&#xff0c;其利润可能来自信用卡的年费、取现手续费、分期手续费、境外交易手续费等。分…

《C++ Primer》第13章 拷贝控制(三)

参考资料&#xff1a; 《C Primer》第5版《C Primer 习题集》第5版 13.5 动态内存管理类&#xff08;P464&#xff09; 某些类需要在运行时分配可变大小的内存空间。这种类通常可以用使用标准库容器来保存它们的数据。有些时候&#xff0c;我们希望类自己进行内存分配&#…

TypeScript【泛型1、泛型2、声明合并、命名空间 、模块1、模块2、声明文件简介】(五)-全面详解(学习总结---从入门到深化)

文章目录 泛型1 泛型2 声明合并 命名空间 模块1 模块2 声明文件简介 泛型1 泛型&#xff08;Generics&#xff09;是指在定义函数、接口或类的时候&#xff0c;不预先指定具体的类型&#xff0c;而在使用的时候再指定类型的一种特性 首先&#xff0c;我们来实现一个函数…

小黑南京归来,参加部里的公务员培训,有点儿社死认识了好多小伙伴的leetcode之旅13. 罗马数字转整数

小黑代码 class Solution:def romanToInt(self, s: str) -> int:chars [M, CM, D, CD, C, XC, L, XL, X, IX, V, IV,I]nums [1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1]map_ dict((k, v) for k,v in zip(chars, nums))# 字符串长度n len(s)# 结果变量res …

安装Redis+Redis设置成windows下的服务+windows无法启动Redis服务,报错误1067:进程意外终止,解决方法

&#xff08;一&#xff09;安装Redis 官网地址&#xff1a;Redis 不过Redis 的官网不提供 Windows 版本的下载&#xff0c;可以从Github上下载&#xff0c;Windows版本的下载地址&#xff1a; https://github.com/microsoftarchive/redis/releases/ 无需安装&#xff0c;直…

2828. 判别首字母缩略词

2828. 判别首字母缩略词 难度: 简单 来源: 每日一题 2023.12.20 给你一个字符串数组 words 和一个字符串 s &#xff0c;请你判断 s 是不是 words 的 首字母缩略词 。 如果可以按顺序串联 words 中每个字符串的第一个字符形成字符串 s &#xff0c;则认为 s 是 words 的…

比例导引(PNG)-Matlab 程序

本文提供比例导引的matlab程序&#xff0c;想要看理论的可以看书《导弹飞行力学》或者我的博客 比例导引详解 代码 %% 三维比例导引末制导clc;clear; close all;%% 设置导弹初始参数和目标参数% 总步长 length 1000000; x_m zeros(length,1); y_m zeros(length,1); z_m z…

node.js mongoose

目录 官方文档 mongoose Schema Model Query document 关系 官方文档 Mongoose v8.0.3: Getting Started mongoose Mongoose 是一个 Node.js 环境下 MongoDB 的对象建模工具。它提供了一种在应用程序中与 MongoDB 数据库进行交互的方式&#xff0c;使得开发者能够使用…

基于k6和python进行自动化性能测试

摘要&#xff1a;在性能测试中&#xff0c;达到相应的性能指标对于一个软件来说十分重要&#xff0c;在本文中&#xff0c;将介绍一种现代化性能测试工具k6。 import http from k6/http; import { sleep } from k6; export default function () {http.get(https://test-api.co…

JNI 注册

一、 JNI 静态注册 ① 在 Android 的 Java 层定义方法 : 在 MainActivity 类中定义 如下 Native 方法 ; public native String stringFromJNI();② Native 方法实现 : 下面是一个 Native 方法实现的示例 ; extern "C" JNIEXPORT jstring JNICALL Java_kim_hsl_jni_…

JMeter如何进行多服务器远程测试

JMeter是Apache软件基金会的开源项目&#xff0c;主要来做功能和性能测试&#xff0c;用Java编写。 我们一般都会用JMeter在本地进行测试&#xff0c;但是受到单个电脑的性能影响&#xff0c;往往达不到性能测试的要求&#xff0c;无法有效的模拟高并发的场景&#xff0c;那么…

css实现0.5px宽度/高度显——属性: transform: scale

在大多数设备上&#xff0c;实际上无法直接使用 CSS 来精确地创建 0.5 像素的边框。因为大多数屏幕的最小渲染单位是一个物理像素&#xff0c;所以通常只能以整数像素单位渲染边框。但是&#xff0c;有一些技巧可以模拟出看起来像是 0.5 像素的边框。 这里介绍使用&#xff1a…

Linux内核模块

文章目录 一、内核模块介绍二、模块讲解1、最简模块代码&#xff1a;2、模块三要素3、常用操作命令3.1、 lsmod&#xff1a;显示已加载模块状态3.2、 insmod&#xff1a;载入模块3.3、rmmod&#xff1a;卸载模块3.4、dmesg&#xff1a;显示信息3.5、modinfo&#xff1a;显示ker…

Azure Machine Learning - 提示工程高级技术

本指南将指导你提示设计和提示工程方面的一些高级技术。 关注TechLead&#xff0c;分享AI全维度知识。作者拥有10年互联网服务架构、AI产品研发经验、团队管理经验&#xff0c;同济本复旦硕&#xff0c;复旦机器人智能实验室成员&#xff0c;阿里云认证的资深架构师&#xff0c…

2023/12/20 work

1. 使用select完成TCP客户端程序 2. 使用poll完成TCP并发服务器 3. 思维导图

linux 内核的 lru_list 的结构

在linux的slab分配的入口slab_alloc有一个传入参数lru&#xff0c;它的作用是使每个slab对象在unused&#xff0c;但可能后面继续使用的时候&#xff0c;不需要free&#xff0c;可以先放在lru_list上。lru_list的结构为&#xff1a; struct list_lru {struct list_lru_node *n…

【Axure RP9】中继器应用及相关案例

一 中继器简介 1.1 中继器是什么 中继器&#xff08;Repeater&#xff09;是一种高级的组件&#xff08;Widget&#xff09;&#xff0c;用于显示文本、图像和其他元素的重复集合。它是一个容器&#xff0c;容器中的每一个项目称作“item”&#xff0c;由于“item”中的数据由…

C# Tcplistener,Tcp服务端简易封装

文章目录 前言相关文章前言设计代码简单使用运行结果 前言 我最近有个需求要写Tcp服务端&#xff0c;我发现Tcp服务端的回调函数比较麻烦&#xff0c;简化Tcp的服务&#xff0c;我打算自己封装一个简单的Tcp服务端。 相关文章 C# TCP应用编程三 异步TCP应用编程 C# Tcpclient…