FSCAN架构以及代码分析
- 简介
- FSCAN架构模式
- 代码层分析
- github.com/shadow1ng/fscan/blob/main/main.go
- https://github.com/shadow1ng/fscan/blob/71ff6e9a0c87b56b7684e50b2f61787d70848005/common/flag.go#L19
- https://github.com/shadow1ng/fscan/blob/71ff6e9a0c87b56b7684e50b2f61787d70848005/common/Parse.go#L14
- https://github.com/shadow1ng/fscan/blob/71ff6e9a0c87b56b7684e50b2f61787d70848005/Plugins/scanner.go#L14
- https://github.com/shadow1ng/fscan/tree/71ff6e9a0c87b56b7684e50b2f61787d70848005/Plugins
- 总结
简介
在面对好工具时,不仅仅工具的功能能够令人欣喜若狂,工具的代码以及开发思路,同样值得人们学习。FSCAN是GO语言开发的一款工具,主要用于内网扫描以及内网信息收集。
https://github.com/shadow1ng/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,美中不足的是锁的用法,太影响效率。作为一个工具而言,功能强大更牛逼。