分析某款go扫描器之五

一、概述

前面几篇文章已经把实现的功能都说完了,这篇主要分析下启动函数,并且说明脚本的参数项作用。

项目来源:https://github.com/XinRoom/go-portScan/blob/main/util/file.go

二、cmd/go-portScan.go

1、设置全局变量

这些变量被定义为在整个程序中使用的全局变量,用于存储命令行参数的值


var (
	ipStr       string
	portStr     string
	pn          bool
	pt          bool
	sT          bool
	rate        int
	sV          bool
	timeout     int
	rateP       int
	hostGroup   int
	iL          string
	devices     bool
	nexthop     string
	httpx       bool
	netLive     bool
	maxOpenPort int
	oCsv        string
	oFile       string
)

2、命令行参数的解析

  • func parseFlag(c *cli.Context)

这个函数的作用是解析命令行参数,并将解析后的值存储到全局变量中。它利用了 cli.Context 对象来获取命令行中各个选项的值,并将这些值存储到程序中事先定义好的全局变量中。

// 从命令行参数获取各个选项的值,并存储到对应的全局变量中
func parseFlag(c *cli.Context) {
	ipStr = c.String("ip")
	iL = c.String("iL")
	portStr = c.String("port")
	nexthop = c.String("nexthop")
	devices = c.Bool("devices")
	pn = c.Bool("Pn")
	rateP = c.Int("rateP")
	hostGroup = c.Int("hostGroup")
	pt = c.Bool("PT")
	rate = c.Int("rate")
	sT = c.Bool("sT")
	sV = c.Bool("sV")
	timeout = c.Int("timeout")
	httpx = c.Bool("httpx")
	netLive = c.Bool("netLive")
	maxOpenPort = c.Int("maxOpenPort")
	oCsv = c.String("oCsv")
	oFile = c.String("oFile")
}
  • func run(c *cli.Context) error

这个函数是应用程序的主要执行逻辑,主要负责根据命令行参数执行相应的操作。它首先根据不同的命令行参数进行一些设置,比如忽略特定信号、初始化日志记录器,并根据特定的选项执行相应的操作。例如:

  • 如果设置了 -nohup 选项,则忽略 SIGHUP 和 SIGTERM 信号。
  • 如果设置了 -devices 选项,则调用 syn.GetAllDevs() 函数来列出设备信息。
  • 如果未提供 IP 或端口参数,则显示应用程序的帮助信息并退出。
  • 如果端口参数是 -,则将其替换为默认的端口范围。

接下来的部分代码,根据不同的参数设置,进行了一系列的初始化和配置操作,包括解析 IP 和端口、初始化并发池、进行扫描任务、收集扫描结果等。

总体而言,run 函数是这个程序的核心逻辑部分,负责根据命令行参数配置和执行相应的扫描任务,以及对结果进行处理和输出。

func run(c *cli.Context) error {
	if c.NumFlags() == 0 {
		cli.ShowAppHelpAndExit(c, 0)
	}
	parseFlag(c)
    // 执行具体的扫描任务
    if c.Bool("nohup") {
        // 忽略SIGHUP和SIGTERM信号
        signal.Ignore(syscall.SIGHUP)
        signal.Ignore(syscall.SIGTERM)
    }
    myLog := util.NewLogger(oFile, true)
    // 检查是否需要列出设备信息
    if devices {
        if r, err := syn.GetAllDevs(); err != nil {
            myLog.Fatal(err.Error())
        } else {
            myLog.Print(r)
        }
        os.Exit(0)
    }
    // 检查IP和端口参数,如果参数为空,则显示应用程序帮助信息并退出
    if ipStr == "" && iL == "" {
        cli.ShowAppHelpAndExit(c, 0)
    }
    if portStr == "-" {
        portStr = "1-65535"
    }
    ipRangeGroup := make([]*iprange.Iter, 0)
	// ip parse
	var firstIp net.IP
	var ips []string
	if ipStr != "" {
		ips = strings.Split(ipStr, ",")
	}
	if iL != "" {
		var err error
		ips, err = util.GetLines(iL)
		if err != nil {
			myLog.Fatalf("open file failed: %s", err.Error())
		}
	}
	for _, _ip := range ips {
		it, startIp, err := iprange.NewIter(_ip)
		if err != nil {
			var iprecords []net.IP
			iprecords, _ = net.LookupIP(_ip)
			if len(iprecords) > 0 {
				_ip = iprecords[0].String()
			} else {
				myLog.Fatalf("[error] %s is not ip/hostname!\n", _ip)
			}
			it, startIp, err = iprange.NewIter(_ip)
			if err != nil {
				myLog.Fatalf("[error] %s is not ip!\n", _ip)
			}
		}
		if firstIp == nil {
			firstIp = startIp
		}
		ipRangeGroup = append(ipRangeGroup, it)
	}

	// netLive
	var wgIpsLive sync.WaitGroup
	// Pool - ipsLive
	poolIpsLive, _ := ants.NewPoolWithFunc(rateP, func(ip interface{}) {
		_ip := ip.([]net.IP)
		for _, ip2 := range _ip {
			if host.IsLive(ip2.String(), pt, time.Duration(tcp.DefaultTcpOption.Timeout)*time.Millisecond) {
				myLog.Printf("[+] %s is live\n", ip2.String())
				break
			}
		}
		wgIpsLive.Done()
	})
	defer poolIpsLive.Release()

	if netLive {
		// 按c段探测
		for _, ir := range ipRangeGroup { // ip group
			for i := uint64(0); i < ir.TotalNum(); i = i + 256 { // ip index
				ip := make(net.IP, len(ir.GetIpByIndex(0)))
				copy(ip, ir.GetIpByIndex(i)) // Note: dup copy []byte when concurrent (GetIpByIndex not to do dup copy)
				ipLastByte := []byte{1, 2, 254, 253, byte(100 + rand.Intn(20)), byte(200 + rand.Intn(20))}
				ips2 := make([]net.IP, 6)
				for j := 0; j < 6; j++ {
					ips2[j] = make(net.IP, len(ip))
					ip[3] = ipLastByte[j]
					copy(ips2[j], ip)
				}
				wgIpsLive.Add(1)
				poolIpsLive.Invoke(ips2)
			}
		}
		wgIpsLive.Wait()
		return nil
	}

	// port parse
	ports, err := port.ShuffleParseAndMergeTopPorts(portStr)
	if err != nil {
		myLog.Fatalf("[error] %s is not port!\n", err)
	}

	// recv
	single := make(chan struct{})
	retChan := make(chan port.OpenIpPort, 5000)

	// ip port num status
	ipPortNumMap := make(map[string]int) // 记录该IP端口开放数量
	var ipPortNumRW sync.RWMutex

	// csv output
	var csvFile *os.File
	var csvWrite *csv.Writer
	if oCsv != "" {
		csvFile, err = os.Create(oCsv)
		if err != nil {
			myLog.Fatalln("[-]", err)
		}
		defer csvFile.Close()
		csvWrite = csv.NewWriter(csvFile)
		csvWrite.Write([]string{"IP", "PORT", "SERVICE", "BANNER", "HTTP_TITLE", "HTTP_STATUS", "HTTP_SERVER", "HTTP_TLS", "HTTP_FINGERS"})
	}

	go func() {
		for ret := range retChan {
			if maxOpenPort > 0 {
				ipPortNumRW.Lock()
				if _, ok := ipPortNumMap[ret.Ip.String()]; ok {
					ipPortNumMap[ret.Ip.String()] += 1
				}
				ipPortNumRW.Unlock()
			}
			myLog.Println(ret.String())
			if csvWrite != nil {
				line := []string{ret.Ip.String(), strconv.Itoa(int(ret.Port)), ret.Service, "", "", "", "", "", ""}
				line[3] = strings.NewReplacer("\\r", "\r", "\\n", "\n").Replace(strings.Trim(strconv.Quote(string(ret.Banner)), "\""))
				if ret.HttpInfo != nil {
					line[4] = ret.HttpInfo.Title
					line[5] = strconv.Itoa(ret.HttpInfo.StatusCode)
					line[6] = ret.HttpInfo.Server
					line[7] = ret.HttpInfo.TlsCN
					line[8] = strings.Join(ret.HttpInfo.Fingers, ",")
				}
				csvWrite.Write(line)
				csvWrite.Flush()
				csvFile.Sync()
			}
		}
		single <- struct{}{}
	}()

	// Initialize the Scanner
	var s port.Scanner
	option := port.Option{
		Rate:        rate,
		Timeout:     timeout,
		NextHop:     nexthop,
		FingerPrint: sV,
		Httpx:       httpx,
	}
	if sT {
		// tcp
		if option.Rate == -1 {
			option.Rate = tcp.DefaultTcpOption.Rate
		}
		if option.Timeout == -1 {
			option.Timeout = tcp.DefaultTcpOption.Timeout
		}
		s, err = tcp.NewTcpScanner(retChan, option)
	} else {
		// syn
		if option.Rate == -1 {
			option.Rate = syn.DefaultSynOption.Rate
		}
		if option.Timeout == -1 {
			option.Timeout = syn.DefaultSynOption.Timeout
		}
		s, err = syn.NewSynScanner(firstIp, retChan, option)
	}
	if err != nil {
		fmt.Fprintf(os.Stderr, "[error] Initialize Scanner: %s\n", err)
		os.Exit(-1)
	}

	start := time.Now()
	var wgPing sync.WaitGroup

	// port scan func
	portScan := func(ip net.IP) {
		var ipPortNum int
		var ipPortNumOk bool
		if maxOpenPort > 0 {
			ipPortNumRW.Lock()
			ipPortNumMap[ip.String()] = 0
			ipPortNumRW.Unlock()
		}
		for _, _port := range ports { // port
			s.WaitLimiter() // limit rate

			if maxOpenPort > 0 {
				ipPortNumRW.RLock()
				ipPortNum, ipPortNumOk = ipPortNumMap[ip.String()]
				ipPortNumRW.RUnlock()
				if ipPortNumOk && ipPortNum >= maxOpenPort {
					break
				}
			}
			s.Scan(ip, _port)
		}
		if maxOpenPort > 0 {
			ipPortNumRW.Lock()
			delete(ipPortNumMap, ip.String())
			ipPortNumRW.Unlock()
		}
	}

	// host group scan func
	var wgHostScan sync.WaitGroup
	hostScan, _ := ants.NewPoolWithFunc(hostGroup, func(ip interface{}) {
		_ip := ip.(net.IP)
		portScan(_ip)
		wgHostScan.Done()
	})
	defer hostScan.Release()

	// Pool - ping and port scan
	poolPing, _ := ants.NewPoolWithFunc(rateP, func(ip interface{}) {
		_ip := ip.(net.IP)
		if host.IsLive(_ip.String(), pt, time.Duration(option.Timeout)*time.Millisecond) {
			wgHostScan.Add(1)
			hostScan.Invoke(_ip)
		}
		wgPing.Done()
	})
	defer poolPing.Release()

	// start scan
	for _, ir := range ipRangeGroup { // ip group
		shuffle := util.NewShuffle(ir.TotalNum())    // shuffle
		for i := uint64(0); i < ir.TotalNum(); i++ { // ip index
			ip := make(net.IP, len(ir.GetIpByIndex(0)))
			copy(ip, ir.GetIpByIndex(shuffle.Get(i))) // Note: dup copy []byte when concurrent (GetIpByIndex not to do dup copy)
			if !pn {                                  // ping
				wgPing.Add(1)
				_ = poolPing.Invoke(ip)
			} else {
				wgHostScan.Add(1)
				hostScan.Invoke(ip)
			}
		}
	}
	wgPing.Wait()     // PING组
	wgHostScan.Wait() // HostGroupS
	s.Wait()          // 扫描器-等
	s.Close()         // 扫描器-收
	<-single          // 接收器-收
	myLog.Printf("[*] elapsed time: %s\n", time.Since(start))
	return nil
}
}

3、主函数

  • 1
  • main 函数是整个程序的入口点。
  • cli.App 结构中,通过设置 NameDescription 来定义应用程序的名称和描述。
  • Action: run 指定当执行命令时调用的函数为 run 函数。
  • Flags 列表包含了各种命令行选项,比如 -ip-port 等。每个选项使用 cli.Flag 定义了选项的名称、用法说明、是否必需以及默认值。
  • 这段代码的作用是初始化一个命令行应用程序,并定义了一系列命令行选项。当程序运行时,它会解析命令行参数,并根据这些参数执行相应的操作,其中 err := app.Run(os.Args) 表示运行应用程序,并使用 os.Args 中的参数作为输入参数。如果运行过程中出现错误,则会将错误信息打印到控制台。

func main() {
	app := &cli.App{
		Name:        "PortScan",
		Description: "High-performance port scanner",
		Action:      run,
		Flags: []cli.Flag{
			&cli.StringFlag{
				Name:     "ip",
				Usage:    "target ip, eg: \"1.1.1.1/30,1.1.1.1-1.1.1.2,1.1.1.1-2\"",
				Required: false,
				Value:    "",
			},
			&cli.StringFlag{
				Name:     "iL",
				Usage:    "target ip file, eg: \"ips.txt\"",
				Required: false,
				Value:    "",
			},
			&cli.StringFlag{
				Name:    "port",
				Aliases: []string{"p"},
				Usage:   "eg: \"top1000,5612,65120,-\"",
				Value:   "top1000",
			},
			&cli.BoolFlag{
				Name:  "Pn",
				Usage: "no ping probe",
				Value: false,
			},
			&cli.IntFlag{
				Name:    "rateP",
				Aliases: []string{"rp"},
				Usage:   "concurrent num when ping probe each ip",
				Value:   300,
			},
			&cli.IntFlag{
				Name:    "hostGroup",
				Aliases: []string{"hp"},
				Usage:   "host concurrent num",
				Value:   200,
			},
			&cli.BoolFlag{
				Name:  "PT",
				Usage: "use TCP-PING mode",
				Value: false,
			},
			&cli.BoolFlag{
				Name:  "sT",
				Usage: "TCP-mode(support IPv4 and IPv6)",
				Value: false,
			},
			&cli.IntFlag{
				Name:    "timeout",
				Aliases: []string{"to"},
				Usage:   "TCP-mode SYN-mode timeout. unit is ms.",
				Value:   800,
			},
			&cli.BoolFlag{
				Name:  "sS",
				Usage: "Use SYN-mode(Only IPv4)",
				Value: true,
			},
			&cli.StringFlag{
				Name:    "nexthop",
				Aliases: []string{"nh"},
				Usage:   "specified nexthop gw add to pcap dev",
				Value:   "",
			},
			&cli.IntFlag{
				Name:    "rate",
				Aliases: []string{"r"},
				Usage:   fmt.Sprintf("number of packets sent per second. If set -1, TCP-mode is %d, SYN-mode is %d(SYN-mode is restricted by the network adapter, 2000=1M)", tcp.DefaultTcpOption.Rate, syn.DefaultSynOption.Rate),
				Value:   -1,
			},
			&cli.BoolFlag{
				Name:    "devices",
				Aliases: []string{"ld"},
				Usage:   "list devices name",
				Value:   false,
			},
			&cli.BoolFlag{
				Name:  "sV",
				Usage: "port service identify",
				Value: false,
			},
			&cli.BoolFlag{
				Name:  "httpx",
				Usage: "http server identify",
				Value: false,
			},
			&cli.BoolFlag{
				Name:  "netLive",
				Usage: "Detect live C-class networks, eg: -ip 192.168.0.0/16,172.16.0.0/12,10.0.0.0/8",
				Value: false,
			},
			&cli.IntFlag{
				Name:    "maxOpenPort",
				Aliases: []string{"mop"},
				Usage:   "Stop the ip scan, when the number of open-port is maxOpenPort",
				Value:   0,
			},
			&cli.StringFlag{
				Name:    "oCsv",
				Aliases: []string{"oC"},
				Usage:   "output csv file",
				Value:   "",
			},
			&cli.StringFlag{
				Name:    "oFile",
				Aliases: []string{"o"},
				Usage:   "output to file",
				Value:   "",
			},
			&cli.BoolFlag{
				Name:  "nohup",
				Usage: "nohup",
				Value: false,
			},
		},
	}

	err := app.Run(os.Args)
	if err != nil {
		fmt.Println("err:", err)
	}
}

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值