FSCAN架构以及代码分析

简介

在面对好工具时,不仅仅工具的功能能够令人欣喜若狂,工具的代码以及开发思路,同样值得人们学习。FSCAN是GO语言开发的一款工具,主要用于内网扫描以及内网信息收集。
https://github.com/shadow1ng/fscan/

FSCAN架构模式

fscan架构

代码层分析

github.com/shadow1ng/fscan/blob/main/main.go

package main

import ( //导包
	"github.com/shadow1ng/fscan/Plugins"
	"github.com/shadow1ng/fscan/common"
)

func main() {
    
     //主函数
	var Info common.HostInfo //  这是结构体
	common.Flag(&Info)  //输出Banner
	common.Parse(&Info) //格式化IP
	Plugins.Scan(Info)  //传入结构体,然后配置进行扫描
	print("scan end\n")
}

https://github.com/shadow1ng/fscan/blob/71ff6e9a0c87b56b7684e50b2f61787d70848005/common/flag.go#L19

package common

import ( //导包
	"flag"
)

func Banner() {
    
     //输出Banner
	banner := `
   ___                              _    
  / _ \     ___  ___ _ __ __ _  ___| | __ 
 / /_\/____/ __|/ __| '__/ _` + "`" + ` |/ __| |/ /
/ /_\\_____\__ \ (__| | | (_| | (__|   <    
\____/     |___/\___|_|  \__,_|\___|_|\_\   
                     fscan version: 1.6.3
`
	print(banner)
}

func Flag(Info *HostInfo) {
    
     //HELP输出展示,以及参数值获取
	Banner()
	flag.StringVar(&Info.Host, "h", "", "IP address of the host you want to scan,for example: 192.168.11.11 | 192.168.11.11-255 | 192.168.11.11,192.168.11.12")
	flag.StringVar(&Info.Ports, "p", DefaultPorts, "Select a port,for example: 22 | 1-65535 | 22,80,3306")
	flag.StringVar(&NoPorts, "pn", "", "the ports no scan,as: -pn 445")
	flag.StringVar(&Info.Command, "c", "", "exec command (ssh)")
	flag.StringVar(&Info.SshKey, "sshkey", "", "sshkey file (id_rsa)")
	flag.StringVar(&Info.Domain, "domain", "", "smb domain")
	flag.StringVar(&Info.Username, "user", "", "username")
	flag.StringVar(&Info.Password, "pwd", "", "password")
	flag.Int64Var(&Info.Timeout, "time", 3, "Set timeout")
	flag.StringVar(&Info.Scantype, "m", "all", "Select scan type ,as: -m ssh")
	flag.StringVar(&Info.Path, "path", "", "fcgi、smb romote file path")
	flag.IntVar(&Threads, "t", 600, "Thread nums")
	flag.StringVar(&HostFile, "hf", "", "host file, -hf ip.txt")
	flag.StringVar(&Userfile, "userf", "", "username file")
	flag.StringVar(&Passfile, "pwdf", "", "password file")
	flag.StringVar(&RedisFile, "rf", "", "redis file to write sshkey file (as: -rf id_rsa.pub) ")
	flag.StringVar(&RedisShell, "rs", "", "redis shell to write cron file (as: -rs 192.168.1.1:6666) ")
	flag.BoolVar(&IsWebCan, "nopoc", false, "not to scan web vul")
	flag.BoolVar(&IsPing, "np", false, "not to ping")
	flag.BoolVar(&Ping, "ping", false, "using ping replace icmp")
	flag.StringVar(&TmpOutputfile, "o", "result.txt", "Outputfile")
	flag.BoolVar(&TmpSave, "no", false, "not to save output log")
	flag.Int64Var(&WaitTime, "debug", 60, "every time to LogErr")
	flag.BoolVar(&Silent, "silent", false, "silent scan")
	flag.StringVar(&URL, "u", "", "url")
	flag.StringVar(&UrlFile, "uf", "", "urlfile")
	flag.StringVar(&Pocinfo.PocName, "pocname", "", "use the pocs these contain pocname, -pocname weblogic")
	flag.StringVar(&Pocinfo.Proxy, "proxy", "", "set poc proxy, -proxy http://127.0.0.1:8080")
	flag.StringVar(&Pocinfo.Cookie, "cookie", "", "set poc cookie")
	flag.Int64Var(&Pocinfo.Timeout, "wt", 5, "Set web timeout")
	flag.IntVar(&Pocinfo.Num, "num", 20, "poc rate")
	flag.Parse()
}

https://github.com/shadow1ng/fscan/blob/71ff6e9a0c87b56b7684e50b2f61787d70848005/common/Parse.go#L14

package common

import ( //导入常用包
	"bufio"
	"flag"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"strconv"
	"strings"
)

func Parse(Info *HostInfo) {
    
     //传递结构体指针。做了统一的接口。很干净。
	ParseScantype(Info)  //配置扫描类型,应该通过不同字符串去调用加载器。
	ParseUser(Info)   //读取用户名
	ParsePass(Info)  //读取密码
	ParseInput(Info) //写入信息
	//后面全是方法的实现过程
}

func ParseUser(Info *HostInfo) {
    
    //读用户列表
	if Info.Username != "" {
    
    
		users := strings.Split(Info.Username, ",")
		for _, user := range users {
    
    
			if user != "" {
    
    
				Info.Usernames = append(Info.Usernames, user)
			}
		}
		for name := range Userdict {
    
    
			Userdict[name] = Info.Usernames
		}
	}
	if Userfile != "" {
    
    
		users, err := Readfile(Userfile)
		if err == nil {
    
    
			for _, user := range users {
    
    
				if user != "" {
    
    
					Info.Usernames = append(Info.Usernames, user)
				}
			}
			for name := range Userdict {
    
    
				Userdict[name] = Info.Usernames
			}
		}
	}

}

func ParsePass(Info *HostInfo) {
    
    //读密码列表
	if Info.Password != "" {
    
    
		passs := strings.Split(Info.Password, ",")
		for _, pass := range passs {
    
    
			if pass != "" {
    
    
				Info.Passwords = append(Info.Passwords, pass)
			}
		}
		Passwords = Info.Passwords
	}
	if Passfile != "" {
    
    
		passs, err := Readfile(Passfile)
		if err == nil {
    
    
			for _, pass := range passs {
    
    
				if pass != "" {
    
    
					Info.Passwords = append(Info.Passwords, pass)
				}
			}
			Passwords = Info.Passwords
		}
	}
	if UrlFile != "" {
    
    
		urls, err := Readfile(UrlFile)
		if err == nil {
    
    
			TmpUrls := make(map[string]struct{
    
    })
			for _, url := range urls {
    
    
				if _, ok := TmpUrls[url]; !ok {
    
    
					TmpUrls[url] = struct{
    
    }{
    
    }
					if url != "" {
    
    
						Urls = append(Urls, url)
					}
				}
			}
		}
	}
}

func Readfile(filename string) ([]string, error) {
    
     //读扫描目标
	file, err := os.Open(filename)
	if err != nil {
    
    
		fmt.Printf("Open %s error, %v\n", filename, err)
		os.Exit(0)
	}
	defer file.Close()
	var content []string
	scanner := bufio.NewScanner(file)
	scanner.Split(bufio.ScanLines)
	for scanner.Scan() {
    
    
		text := strings.TrimSpace(scanner.Text())
		if text != "" {
    
    
			content = append(content, scanner.Text())
		}
	}
	return content, nil
}

func ParseInput(Info *HostInfo) {
    
    //判断目标是否存在 然后写入信息包括端口
	if Info.Host == "" && HostFile == "" && URL == "" && UrlFile == "" {
    
    
		fmt.Println("Host is none")
		flag.Usage()
		os.Exit(0)
	}

	if TmpOutputfile != "" {
    
    
		if !strings.Contains(Outputfile, "/") && !strings.Contains(Outputfile, "\\") 
		{
    
    
			Outputfile = getpath() + TmpOutputfile
		} else {
    
    
			Outputfile = TmpOutputfile
		}
	}
	if TmpSave == true {
    
    
		IsSave = false
	}
	if Info.Ports == DefaultPorts {
    
    
		Info.Ports += Webport
	}
}

func ParseScantype(Info *HostInfo) {
    
     //设置扫描类型
	_, ok := PORTList[Info.Scantype]
	if !ok {
    
    
		showmode()
	}
	if Info.Scantype != "all" {
    
    
		if Info.Ports == DefaultPorts {
    
    
			switch Info.Scantype {
    
    
			case "web":
				Info.Ports = Webport
			case "ms17010":
				Info.Ports = "445"
			case "cve20200796":
				Info.Ports = "445"
			case "main":
				Info.Ports = DefaultPorts
			default:
				port, _ := PORTList[Info.Scantype]
				Info.Ports = strconv.Itoa(port)
			}
			fmt.Println("-m ", Info.Scantype, " start scan the port:", Info.Ports)
		}
	}
}

func CheckErr(text string, err error) {
    
     //异常输出
	if err != nil {
    
    
		fmt.Println(text, err.Error())
		os.Exit(0)
	}
}

func getpath() string {
    
     //获取路径
	file, _ := exec.LookPath(os.Args[0])
	path1, _ := filepath.Abs(file)
	filename := filepath.Dir(path1)
	var path string
	if strings.Contains(filename, "/") {
    
    
		tmp := strings.Split(filename, `/`)
		tmp[len(tmp)-1] = ``
		path = strings.Join(tmp, `/`)
	} else if strings.Contains(filename, `\`) {
		tmp := strings.Split(filename, `\`)
		tmp[len(tmp)-1] = ``
		path = strings.Join(tmp, `\`)
	}
	return path
}

func showmode() {
    
     //模式选择错误
	fmt.Println("The specified scan type does not exist")
	fmt.Println("-m")
	for name := range PORTList {
    
    
		fmt.Println("   [" + name + "]")
	}
	os.Exit(0)
}

https://github.com/shadow1ng/fscan/blob/71ff6e9a0c87b56b7684e50b2f61787d70848005/Plugins/scanner.go#L14

package Plugins

import (
	"errors"
	"fmt"
	"github.com/shadow1ng/fscan/WebScan/lib"
	"github.com/shadow1ng/fscan/common"
	"reflect"
	"strconv"
	"strings"
	"sync"
)

func Scan(info common.HostInfo) {
    
     //进入扫描加载模块
	fmt.Println("start infoscan")
	Hosts, _ := common.ParseIP(info.Host, common.HostFile)
	lib.Inithttp(common.Pocinfo)
	var ch = make(chan struct{
    
    }, common.Threads)
	var wg = sync.WaitGroup{
    
    }
	if len(Hosts) > 0 {
    
    
		if common.IsPing == false {
    
    
			Hosts = ICMPRun(Hosts, common.Ping)//使用ping判断主机
			fmt.Println("icmp alive hosts len is:", len(Hosts))
		}
		if info.Scantype == "icmp" {
    
    
			return
		}
		AlivePorts := PortScan(Hosts, info.Ports, info.Timeout) //端口扫描
		fmt.Println("alive ports len is:", len(AlivePorts))
		if info.Scantype == "portscan" {
    
    
			return
		}

		var severports []string //severports := []string{"21","22","135"."445","1433","3306","5432","6379","9200","11211","27017"...}
		//指定的端口
		for _, port := range common.PORTList {
    
    
			severports = append(severports, strconv.Itoa(port))
		}
		fmt.Println("start vulscan")
		for _, targetIP := range AlivePorts {
    
    
			info.Host, info.Ports = strings.Split(targetIP, ":")[0], strings.Split(targetIP, ":")[1] 
			//根据开放端口 加入不同的漏洞插件进行扫描
			if info.Scantype == "all" {
    
    
				switch {
    
    
				case info.Ports == "445":
					//AddScan(info.Ports, info, ch, &wg)  //smb
					AddScan("1000001", info, ch, &wg) //ms17010
					AddScan("1000002", info, ch, &wg) //smbghost
				case info.Ports == "9000":
					AddScan(info.Ports, info, ch, &wg) //fcgiscan
					AddScan("1000003", info, ch, &wg)  //http
				case IsContain(severports, info.Ports):
					AddScan(info.Ports, info, ch, &wg) //plugins scan
				default:
					AddScan("1000003", info, ch, &wg) //webtitle
				}
			} else {
    
    
				port, _ := common.PORTList[info.Scantype]
				scantype := strconv.Itoa(port)
				AddScan(scantype, info, ch, &wg)
			}
		}
	}
	if common.URL != "" {
    
    
		info.Url = common.URL
		AddScan("1000003", info, ch, &wg)
	}
	if len(common.Urls) > 0 {
    
    
		for _, url := range common.Urls {
    
    
			info.Url = url
			AddScan("1000003", info, ch, &wg)
		}
	}
	wg.Wait()
	common.LogWG.Wait()
	close(common.Results)
	fmt.Println(fmt.Sprintf("已完成 %v/%v", common.End, common.Num))
}

var Mutex = &sync.Mutex{
    
    }

func AddScan(scantype string, info common.HostInfo, ch chan struct{
    
    }, wg *sync.WaitGroup) {
    
    
//插件式扫描,通过加载插件列表然后开始扫描。锁的效率堪忧。
	wg.Add(1)
	go func() {
    
    
		Mutex.Lock()
		common.Num += 1
		Mutex.Unlock()
		ScanFunc(PluginList, scantype, &info)
		wg.Done()
		Mutex.Lock()
		common.End += 1
		Mutex.Unlock()
		<-ch
	}()
	ch <- struct{
    
    }{
    
    }
}

func ScanFunc(m map[string]interface{
    
    }, name string, infos ...interface{
    
    }) (result []reflect.Value, err error) {
    
     //这是一个扫描接口
	f := reflect.ValueOf(m[name])
	if len(infos) != f.Type().NumIn() {
    
    
		err = errors.New("The number of infos is not adapted ")
		fmt.Println(err.Error())
		return result, nil
	}
	in := make([]reflect.Value, len(infos))
	for k, info := range infos {
    
    
		in[k] = reflect.ValueOf(info)
	}
	result = f.Call(in)
	return result, nil
}

func IsContain(items []string, item string) bool {
    
    
	for _, eachItem := range items {
    
    
		if eachItem == item {
    
    
			return true
		}
	}
	return false
}

https://github.com/shadow1ng/fscan/tree/71ff6e9a0c87b56b7684e50b2f61787d70848005/Plugins

插件实现方法主要参考漏洞原理,不再多说,插件列表如下:
在这里插入图片描述

总结

结构干净,插件部分可以自己扩充。总体感觉比较NICE,美中不足的是锁的用法,太影响效率。作为一个工具而言,功能强大更牛逼。

猜你喜欢

转载自blog.csdn.net/qq_35476650/article/details/119536978
今日推荐