分析某款go端口扫描器之二

2023-12-14 13:20

本文主要是介绍分析某款go端口扫描器之二,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

一、概述

本次主要分析指纹识别部分,针对开放http的端口的服务信息进行识别。

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

二、core--port-fingerprint-webfinger目录

1、fingerprint-webfinger-faviconhash.go

此文件的功能,主要是从html页面中提取favicon的url,然后计算favicon的hash值

此文件中主要包含三个方法:

  • func FindFaviconUrl(body string) string

从 HTML 页面的字符串中提取 favicon 的 URL;使用正则表达式 shortcutText 匹配包含 favicon 的 <link> 标签,然后再使用 shortcutHref 提取其 href 属性中的 URL

  • func mmh3Hash32(raw []byte) string

计算给定字节切片的 MurmurHash3 32 位哈希值;使用 MurmurHash3 算法对输入的字节切片进行哈希运算,返回结果作为字符串表示。

  • func standBase64(braw []byte) []byte

对二进制数据进行标准的 Base64 编码,并按行分割。将输入的二进制数据使用标准的 Base64 编码成字符串,然后按照每行字符数为 76 的标准,将编码后的结果分割成多行。

package webfingerimport ("bytes""encoding/base64""fmt""github.com/twmb/murmur3""regexp"
)var (shortcutText = regexp.MustCompile(`(?im)<link.*?rel=["']?shortcut icon["']?.*?>`)//提取link标签的正则表达式shortcutHref = regexp.MustCompile(`(?im)href=['"]+(.*?)['"]+`)//提取link标签里的href值的正则表达式
)func FindFaviconUrl(body string) string {a := shortcutText.FindStringSubmatch(body)//首先根据正则提取传入的html页面的link标签,赋值给aif len(a) > 0 {//如果存在link标签faviconLink := a[0]b := shortcutHref.FindStringSubmatch(faviconLink)//提取第一个link标签的href的值if len(b) > 1 {return b[1] //如果提取url成功,则返回url}}return ""//不存在link标签,返回空
}func mmh3Hash32(raw []byte) string {var h32 = murmur3.New32()_, err := h32.Write(raw)if err == nil {return fmt.Sprintf("%d", int32(h32.Sum32()))//使用 MurmurHash3 算法对输入的字节切片进行哈希运算,返回结果作为字符串表示。} else {return ""}
}func standBase64(braw []byte) []byte {bckd := base64.StdEncoding.EncodeToString(braw)var buffer bytes.Bufferfor i := 0; i < len(bckd); i++ {ch := bckd[i]buffer.WriteByte(ch)if (i+1)%76 == 0 {buffer.WriteByte('\n')//将输入的二进制数据使用标准的 Base64 编码成字符串,然后按照每行字符数为 76 的标准,将编码后的结果分割成多行。}}buffer.WriteByte('\n')return buffer.Bytes()
}

2、fingerprint-webfinger-finger.json

这个文件主要为faviconhash的字典,用来通过faviconhash的值比对来获取指纹信息。

3、fingerprint-webfinger-finger.go

此文件代码是一个 Web 系统指纹识别器。它的主要目的是从 HTTP 响应或 Favicon.ico 数据中识别 Web 服务的指纹。下面是一步步的解释:

  • type Date struct
  • type WebFinger struct 

Date 结构体表示特定指纹识别方法的数据结构;

WebFinger 结构体用于保存不同的指纹集合

type Date struct {Name     string //指纹名称Location string  //在http响应中的位置Method   string  //指纹识别方法(如keyword、regular、faviconhash)Keyword  []string //关键字或者正则表达式数组
}type WebFinger struct {Name    string    //指纹集合的名称Fingers []Date    //包含data结构体的切片
}var WebFingers []WebFinger//go:embed finger.json
var DefFingerData []byte
  • func ParseWebFingerData(data []byte) error

解析 Web 指纹数据。

func ParseWebFingerData(data []byte) error {err := json.Unmarshal(data, &WebFingers)  //将json数据保存到WebFingers实例中if err != nil {return err}return nil
}
  • func LoadWebFingerData(file string) error

从文件中加载 Web 指纹数据。

//LoadWebFingerData 加载web指纹数据
func LoadWebFingerData(file string) error {data, err := os.ReadFile(file)if err != nil {return err}err = ParseWebFingerData(data)if err != nil {return err}return nil
}
  • func web FingerIdent(resp *http.Response) (names []string)

此函数解析 HTTP 响应的内容和头部信息,然后遍历 WebFingers 中的指纹集合,检查是否匹配给定的指纹识别方法和关键字或正则表达式;如果匹配,则返回匹配的指纹名称。 

  •  解析 HTTP 响应的内容和头部信息。
  • 遍历 WebFingers 中的指纹集合,检查是否匹配给定的指纹识别方法和关键字或正则表达式。
  • 如果匹配,则返回匹配的指纹名称。
//WebFingerIdent web系统指纹识别 用指纹库的指纹循环比对response中header部分和body部分是否包含特征
func WebFingerIdent(resp *http.Response) (names []string){var dataMap = make(map[string]string)body, _ := io.ReadAll(resp.Body)dataMap["body"] = string(body)var b bytes.Bufferresp.Header.Write(&b)dataMap["header"] = b.String()for _,finger :=  range WebFingers {for _, finger2 := range finger.Fingers {var flag boolif _, ok := dataMap[finger2.Location]; !ok {//取指纹库中Location字段为body和header部分的指纹continue}switch finger2.Method {case "keyword" :if iskeyword(dataMap[finger2.Location], finger2.Keyword){flag = true}case "regular" :if isregular(dataMap[finger2.Location], finger2.Keyword){flag = true}}if flag {if finger2.Name != ""{finger.Name += "," + finger2.Name}names = append(names, finger.Name)break}}}return}
  • func WebFingerIdentByFavicon(body []byte) (names []string)

 此函数通过 Favicon.ico 识别 Web 系统的指纹。首先对对 Favicon.ico 数据进行哈希运算,然后遍历WebFingers 中的指纹集合,检查是否与哈希值匹配指定的指纹识别方法和关键字。如果匹配,则返回匹配的指纹名称。

  • 对 Favicon.ico 数据进行哈希运算。
  • 遍历 WebFingers 中的指纹集合,检查是否与哈希值匹配指定的指纹识别方法和关键字。
  • 如果匹配,则返回匹配的指纹名称。
// WebFingerIdentByFavicon web系统指纹识别,通过Favicon.ico用指纹库的指纹循环比对favico hash之判断是否包含特征
func WebFingerIdentByFavicon(body []byte) (names []string) {var data stringdata = mmh3Hash32(standBase64(body))for _, finger := range WebFingers {for _, finger2 := range finger.Fingers {switch finger2.Method {case "faviconhash":if data != "" && len(finger2.Keyword) > 0 && data == finger2.Keyword[0] {if finger2.Name != "" {finger.Name += "," + finger2.Name}names = append(names, finger.Name)break}}}}return
}

4、fingerprint-webfinger-matchfinger.go

此文件包含两个方法,iskeyword和isregular,都是用于检查字符串中是否存在特定的关键字或匹配正则表达式

  • func iskeyword(str string, keyword []string) bool

检查字符串中是否存在指定的关键字。遍历关键字数组,如果字符串中不包含其中任何一个关键字,则立即返回 false,否则返回 true

  • func isregular(str string, keyword []string) bool

检查字符串是否匹配指定的正则表达式。遍历正则表达式数组,使用 regexp.MustCompile 创建正则表达式对象,然后检查字符串是否与每个正则表达式匹配。如果字符串不匹配任何一个正则表达式,则立即返回 false,否则返回 true

func iskeyword(str string, keyword []string) bool {if len(keyword) == 0 || str == "" {//先判断传入关键字切片是否为空和字符串是否为空return false}//遍历关键字数组,如果字符串中不包含其中任何一个关键字,则立即返回 for _, k := range keyword {//循环遍历keyword切片,检查keyword是否都包含在str字符串中if !strings.Contains(str, k) {return false}}return true
}//遍历正则表达式数组,使用 regexp.MustCompile 创建正则表达式对象,然后检查字符串是否与每个正则表达式匹配。如果字符串不匹配任何一个正则表达式,则立即返回 false,否则返回 true。
func isregular(str string, keyword []string) bool {if len(keyword) == 0 || str == "" {return false}for _, k := range keyword {re := regexp.MustCompile(k)if !re.Match([]byte(str)) {return false}}return true
}

5、辅助函数

  • mmh3Hash32(raw []byte) string:对数据进行哈希并返回哈希值的字符串表示形式。
  • standBase64(braw []byte) []byte:对数据进行 Base64 编码,并在每行末尾添加换行符。

三、core-port-fingerprint目录

1、core-port-fingerprint-encodings.go

此文件主要是一些用于字符编码转换的函数,以及一个用于从http响应中提取标题并根据响应的内容类型进行相应的解码的函数。

  • func Decodegbk(s []byte) ([]byte, error)

这个函数用于将 GBK 编码的字节序列转换为 UTF-8 编码的字节序列。它使用 simplifiedchinese.GBK.NewDecoder() 进行解码,将输入的 GBK 编码字节流转换为 UTF-8 编码

//将GBK编码转化UTF-编码
func Decodegbk(s []byte) ([]byte, error){I := bytes.NewReader(s)O := transform.NewReader(I, simplifiedchinese.GBK.NewDecoder())d, e := ioutil.ReadAll(O)if e != nil {return nil, e}return d, nil}
  • func Decodebig5(s []byte) ([]byte, error)

类似于 Decodegbk,这个函数将 BIG5 编码的字节序列转换为 UTF-8 编码的字节序列,使用 traditionalchinese.Big5.NewDecoder() 进行解码。

func Decodebig5(s []byte) ([]byte, error){I := bytes.NewReader(s)O := transform.NewReader(I, traditionalchinese.Big5.NewDecoder())d, e := ioutil.ReadAll(O)if e != nil {return nil, e}return d, nil
}

  • func Encodebig5(s []byte) ([]byte, error)

这个函数是将 UTF-8 编码的字节序列转换为 BIG5 编码的字节序列,使用 traditionalchinese.Big5.NewEncoder() 进行编码。

func Encodebig5(s []byte) ([]byte, error){I := bytes.NewReader(s)O := transform.NewReader(I, traditionalchinese.Big5.NewEncoder())d, e := ioutil.ReadAll(O)if e != nil {return nil, e }return d,nil
}
  • func DecodeKorean(s []byte) ([]byte, error)

这个函数是用于将韩文编码的字节序列转换为 UTF-8 编码的字节序列,使用 korean.EUCKR.NewDecoder() 进行解码。

func DecodeKorean(s []byte) ([]byte, error) {koreanDecoder := korean.EUCKR.NewDecoder()return koreanDecoder.Bytes(s)
}
  • func DecodeData(data []byte, headers http.Header) ([]byte, error)

这个函数从 HTTP 响应中提取数据,并根据响应的内容类型进行相应的解码。它会检查响应头中的 Content-Type,尝试根据特定的字符集(如 GBK、EUC-KR 等)对数据进行解码。如果检测到响应头中指定了字符集,会调用相应的解码函数进行转换,如果没有匹配到特定的字符集,就会尝试从 HTML 头部的 meta 标签中提取字符集信息,并根据提取的信息进行解码。最终返回解码后的数据或原始数据(如果未指定字符集或解码失败)。

func DecodeData(data []byte, headers http.Header) ([]byte, error) {//Non UTF-8if contentTypes, ok := headers["Content-Type"]; ok {contentType := strings.ToLower(strings.Join(contentTypes, ";"))//用;连接contenttypes内容,并转化为小写//根据不同的contentyps值调用不同的解密方法switch {case stringsutil.ContainsAny(contentType, "charset=gb2312","charset=gbk"):return Decodegbk([]byte(data))case stringsutil.ContainsAyn(contentType,"euc-kr"):return DecodeDorean(data)    }//Content-Type 来自 head tagvar match = reContentType.FindSubmatch(data)//reContentType来自 core/port/fingerprint/title.go ,作用为regexp.MustCompile(`(?im)\s*charset="(.*?)"|charset=(.*?)"\s*`),根据正则提取对应字段var mcontentType = ""if len(match) != 0 {for i, v := range match{if string(v) != "" && i != 0 {mcontentType = string(v)}}mcontentType = strings.ToLower(mcontentType)}switch {case stringsutil.ContainsAny(mcontentType, "gb2312", "gbk"):return Decodegbk(data)}}return data, nil}    

ps:这些函数的作用在于根据不同的字符编码格式,将原始的字符序列转换为 UTF-8 编码的字符序列,使其能够被正确解析和处理。DecodeData 函数则是根据 HTTP 响应的内容类型来判断使用何种字符编码转换函数,以确保正确地解码数据

2、core-port-fingerprint-fingerprint.go

这个文件主要用于端口服务的指纹识别,主要功能如下:

首先创建常量和类型:

type Action uint8const (ActionRecv = Action(iota)ActionSend
)const (refusedStr   = "refused"ioTimeoutStr = "i/o timeout"
)type ruleData struct {Action  Action // send or recvData    []byte // send or match dataRegexps []*regexp.Regexp
}type serviceRule struct {Tls       boolDataGroup []ruleData
}var serviceRules = make(map[string]serviceRule)
var readBufPool = &sync.Pool{New: func() interface{} {return make([]byte, 4096)},
}

函数如下:

  • func PortIdentify(network string, ip net.IP, _port uint16, dailTimeout time.Duration) (serviceName string, isDailErr bool)

此函数识别端口对应的服务。它按照指定的顺序尝试从一系列服务规则中匹配出对应的服务。如果连接失败或者无法匹配出服务,则返回"unknown"

// PortIdentify 端口识别
func PortIdentify(network string, ip net.IP, _port uint16, dailTimeout time.Duration) (serviceName string, isDailErr bool) {matchedRule := make(map[string]struct{})unknown := "unknown"var matchStatus int// 优先判断port可能的服务if serviceNames, ok := portServiceOrder[_port]; ok {//portServiceOrder为rule.go文件中常见端口服务对应列表for _, service := range serviceNames {matchedRule[service] = struct{}{}matchStatus = matchRule(network, ip, _port, serviceRules[service], dailTimeout)//获取服务端口开放状态if matchStatus == 1 {return service, false} else if matchStatus == -1 {return unknown, true}}}// onlyRecv{var conn net.Connvar n intbuf := readBufPool.Get().([]byte)defer func() {readBufPool.Put(buf)}()address := fmt.Sprintf("%s:%d", ip, _port)conn, _ = net.DialTimeout(network, address, dailTimeout)if conn == nil {return unknown, true}n, _ = read(conn, buf)conn.Close()if n != 0 {for _, service := range onlyRecv {_, ok := matchedRule[service] //检测是不是已有服务if ok {continue}matchStatus = matchRuleWhithBuf(buf[:n], ip, _port, serviceRules[service])//判断端口服务开放状态if matchStatus == 1 {return service, false}}}for _, service := range onlyRecv {matchedRule[service] = struct{}{}}}// 优先判断Top服务for _, service := range serviceOrder {_, ok := matchedRule[service]if ok {continue}matchedRule[service] = struct{}{}matchStatus = matchRule(network, ip, _port, serviceRules[service], dailTimeout)if matchStatus == 1 {return service, false} else if matchStatus == -1 {return unknown, true}}// otherfor service, rule := range serviceRules {_, ok := matchedRule[service]if ok {continue}matchStatus = matchRule(network, ip, _port, rule, dailTimeout)if matchStatus == 1 {return service, false} else if matchStatus == -1 {return unknown, true}}return unknown, false
}

  • func matchRuleWhithBuf(buf, ip net.IP, _port uint16, serviceRule serviceRule) int

函数根据传入的数据缓冲区,匹配给定的服务规则。如果规则中的数据在缓冲区中找到了匹配,则返回成功状态。

// 指纹匹配函数
func matchRuleWhithBuf(buf, ip net.IP, _port uint16, serviceRule serviceRule) int {data := []byte("")// 逐个判断for _, rule := range serviceRule.DataGroup {if rule.Data != nil {//先替换请求中的数据data数据,比如IP和portdata = bytes.Replace(rule.Data, []byte("{IP}"), []byte(ip.String()), -1)data = bytes.Replace(data, []byte("{PORT}"), []byte(strconv.Itoa(int(_port))), -1)}// 包含数据就正确,使用正则匹配if rule.Regexps != nil {for _, _regex := range rule.Regexps {if _regex.MatchString(convert2utf8(string(buf))) {return 1}}}if bytes.Compare(data, []byte("")) != 0 && bytes.Contains(buf, data) {return 1}}return 0
}

  • func matchRule(network string, ip net.IP, _port uint16, serviceRule serviceRule, dailTimeout time.Duration) int

此函数建立连接并根据连接的情况进行数据的发送和接收,然后匹配给定的服务规则。如果规则中的数据在接收的数据中找到了匹配,则返回成功状态。

// 指纹匹配函数
func matchRule(network string, ip net.IP, _port uint16, serviceRule serviceRule, dailTimeout time.Duration) int {var err errorvar isTls boolvar conn net.Connvar connTls *tls.Connaddress := fmt.Sprintf("%s:%d", ip, _port)// 建立连接if serviceRule.Tls {//先确定是不是https// tlsconnTls, err = tls.DialWithDialer(&net.Dialer{Timeout: dailTimeout}, network, address, &tls.Config{InsecureSkipVerify: true,MinVersion:         tls.VersionTLS10,})if err != nil {if strings.HasSuffix(err.Error(), ioTimeoutStr) || strings.Contains(err.Error(), refusedStr) {return -1}return 0}defer connTls.Close()isTls = true} else {//如果不是https,使用net.conn连接conn, err = net.DialTimeout(network, address, dailTimeout)if conn == nil {return -1}defer conn.Close()}buf := readBufPool.Get().([]byte)defer func() {readBufPool.Put(buf)}()data := []byte("")// 逐个判断for _, rule := range serviceRule.DataGroup {if rule.Data != nil {//替换所有data数据中的IP和port,针对http和httpsdata = bytes.Replace(rule.Data, []byte("{IP}"), []byte(ip.String()), -1)data = bytes.Replace(data, []byte("{PORT}"), []byte(strconv.Itoa(int(_port))), -1)}if rule.Action == ActionSend {//先判断获取发送数据,进行conn连接if isTls {connTls.SetWriteDeadline(time.Now().Add(time.Second))_, err = connTls.Write(data)} else {conn.SetWriteDeadline(time.Now().Add(time.Second))_, err = conn.Write(data)}if err != nil {// 出错就退出return 0}} else {//针对recive进行数据判断var n intif isTls {n, err = read(connTls, buf)} else {n, err = read(conn, buf)}// 出错就退出if n == 0 {return 0}// 包含数据就正确  使用正则筛选返回包数据if rule.Regexps != nil {for _, _regex := range rule.Regexps {if _regex.MatchString(convert2utf8(string(buf[:n]))) {return 1}}}//判断数据是否为空,以及buf中是否包含数据if bytes.Compare(data, []byte("")) != 0 && bytes.Contains(buf[:n], data) {return 1}}}return 0
}

  • func read(conn interface{}, buf []byte) (int, error)

函数负责从连接中读取数据,并在指定时间内设置读取的截止时间。它会尝试从传入的连接中读取数据到缓冲区中,并返回读取的字节数和可能出现的错误。如果读取的数据长度为 0 或者发生错误,则会返回相应的状态信息。

func read(conn interface{}, buf []byte) (int, error) {switch conn.(type) {case net.Conn:conn.(net.Conn).SetReadDeadline(time.Now().Add(time.Second))return conn.(net.Conn).Read(buf[:])case *tls.Conn:conn.(*tls.Conn).SetReadDeadline(time.Now().Add(time.Second))return conn.(*tls.Conn).Read(buf[:])}return 0, errors.New("unknown type")
}

  • func convert2utf8(src string) string

函数用于修复正则表达式在匹配非UTF-8字符时可能出现的问题。它会将UTF-8以外的字符转换成对应的UTF-8字符,确保正则表达式可以正确匹配。

func convert2utf8(src string) string {var dst stringfor i, r := range src {var v stringif r == utf8.RuneError {// convert, rune => string, intstring() => encoderune()v = string(src[i])} else {v = string(r)}dst += v}return dst
}

 ps:代码中利用 sync.Pool 作为缓冲池,用来存储读取数据的缓冲区,提高效率。总体来说,这些函数实现了通过发送和接收数据并根据特定规则匹配服务的功能,用于识别特定端口上可能运行的服务

3、core-port-fingerprint-rules.go

这个文件主要为一个指纹识别器,它尝试根据不同服务的特征来判断特定端口上的服务。这里的 serviceOrder 是服务的顺序列表,onlyRecv 是仅接收数据的服务列表,portServiceOrder 是特定端口和服务的映射关系。

var serviceOrder = []string{"http", "https", "ssh", "redis", "mysql"}var onlyRecv []stringvar portServiceOrder = map[uint16][]string{21:    {"ftp"},22:    {"ssh"},80:    {"http", "https"},443:   {"https", "http"},445:   {"smb"},1035:  {"oracle"},1080:  {"socks5", "socks4"},1081:  {"socks5", "socks4"},1082:  {"socks5", "socks4"},1083:  {"socks5", "socks4"},1433:  {"sqlserver"},1521:  {"oracle"},1522:  {"oracle"},1525:  {"oracle"},1526:  {"oracle"},1574:  {"oracle"},1748:  {"oracle"},1754:  {"oracle"},3306:  {"mysql"},3389:  {"ms-wbt-server"},6379:  {"redis"},9001:  {"mongodb"},11211: {"memcached"},14238: {"oracle"},27017: {"mongodb"},20000: {"oracle"},49153: {"mongodb"},
}
  • func init()

init() 函数中,为了不同的服务(如HTTP、HTTPS、SSH等),定义了对应的识别规则。每个服务都有其特定的数据发送和接收规则,用于匹配返回的数据以确定服务是否存在。例如,对于 HTTP 服务,它尝试发送一个 HTTP 头部,并尝试匹配返回数据中是否包含 HTTP/;对于 SSH 服务,它会尝试匹配返回数据中是否包含 SSH 相关的标识等。这些规则都是通过发送特定数据和匹配接收到的数据来进行识别的

func init() {// httpserviceRules["http"] = serviceRule{ //pingerprint.go中结构体,包含Tls和DataGroupTls: false,DataGroup: []ruleData{{//设置http和https的规则数据,包含请求头和接收包ActionSend,[]byte("HEAD / HTTP/1.1\r\nHost: {IP}\r\nUser-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:91.0) Gecko/20100101 Firefox/91.0\r\nAccept: */*\r\nAccept-Language: en\r\nAccept-Encoding: deflate\r\n\r\n"),nil,},{ActionRecv,[]byte("HTTP/"),nil,},},}// httpsserviceRules["https"] = serviceRule{Tls:       true,DataGroup: serviceRules["http"].DataGroup,}// sshserviceRules["ssh"] = serviceRule{Tls: false,DataGroup: []ruleData{{//设置ssh的匹配规则ActionRecv,nil,[]*regexp.Regexp{regexp.MustCompile(`^SSH-([\d.]+)-`),regexp.MustCompile(`^SSH-(\d[\d.]+)-`),regexp.MustCompile(`^SSH-(\d[\d.]*)-`),regexp.MustCompile(`^SSH-2\.0-`),regexp.MustCompile(`^SSH-1\.`),},},},}// ftpserviceRules["ftp"] = serviceRule{Tls: false,DataGroup: []ruleData{{//设置ftp的匹配规则ActionRecv,nil,[]*regexp.Regexp{regexp.MustCompile(`^220 ([-/.+\w]+) FTP server`),regexp.MustCompile(`^220[ |-](.*?)FileZilla`),regexp.MustCompile(`^(?i)220[ |-](.*?)version`),regexp.MustCompile(`^220 3Com `),regexp.MustCompile(`^220-GuildFTPd`),regexp.MustCompile(`^220-.*\r\n220`),regexp.MustCompile(`^220 Internet Rex`),regexp.MustCompile(`^530 Connection refused,`),regexp.MustCompile(`^220 IIS ([\w._-]+) FTP`),regexp.MustCompile(`^220 PizzaSwitch `),regexp.MustCompile(`(?i)^220 ([-.+\w]+) FTP`),regexp.MustCompile(`(?i)^220[ |-](.*?)FTP`),},},},}// socks4serviceRules["socks4"] = serviceRule{Tls: false,DataGroup: []ruleData{{//设置socks4发送包的数据部分ActionSend,[]byte("\x04\x01\x00\x16\x7f\x00\x00\x01rooo\x00"),nil,},{//设置socks4接收包的匹配规则ActionRecv,nil,[]*regexp.Regexp{regexp.MustCompile(`^\x00\x5a`),regexp.MustCompile(`^\x00\x5b`),regexp.MustCompile(`^\x00\x5c`),regexp.MustCompile(`^\x00\x5d`),},},},}// socks5serviceRules["socks5"] = serviceRule{Tls: false,DataGroup: []ruleData{{//设置socks5的发送包数据ActionSend,[]byte("\x05\x04\x00\x01\x02\x80\x05\x01\x00\x03\x0dwww.baidu.com\x00\x50GET / HTTP/1.0\r\n\r\n"),nil,},{//设置socks5的接收包规则ActionRecv,nil,[]*regexp.Regexp{regexp.MustCompile(`^\x05\x00\x05\x01`),regexp.MustCompile(`^\x05\x00\x05\x00\x00\x01.{6}HTTP`),regexp.MustCompile(`^\x05\x02`),regexp.MustCompile(`^\x05\x00`),},},},}tls//serviceRules["tls"] = serviceRule{//	Tls: false,//	DataGroup: []ruleData{//		{//			ActionSend,//			[]byte("\x16\x03\x00\x00S\x01\x00\x00O\x03\x00?G\xd7\xf7\xba,\xee\xea\xb2`~\xf3\x00\xfd\x82{\xb9\xd5\x96\xc8w\x9b\xe6\xc4\xdb<=\xdbo\xef\x10n\x00\x00(\x00\x16\x00\x13\x00\x0a\x00f\x00\x05\x00\x04\x00e\x00d\x00c\x00b\x00a\x00`\x00\x15\x00\x12\x00\x09\x00\x14\x00\x11\x00\x08\x00\x06\x00\x03\x01\x00"),//			nil,//		},//		{//			ActionRecv,//			nil,//			[]*regexp.Regexp{//				regexp.MustCompile(`^[\x16\x15]\x03\x00`),//				regexp.MustCompile(`^[\x16\x15]\x03...\x02`),//			},//		},//	},//}// smbserviceRules["smb"] = serviceRule{Tls: false,DataGroup: []ruleData{{//设置smb协议的发送包数据ActionSend,[]byte("\x00\x00\x00\xa4\xff\x53\x4d\x42\x72\x00\x00\x00\x00\x08\x01\x40\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x40\x06\x00\x00\x01\x00\x00\x81\x00\x02PC NETWORK PROGRAM 1.0\x00\x02MICROSOFT NETWORKS 1.03\x00\x02MICROSOFT NETWORKS 3.0\x00\x02LANMAN1.0\x00\x02LM1.2X002\x00\x02Samba\x00\x02NT LM 0.12\x00\x02NT LANMAN 1.0\x00"),nil,},{//设置smb协议的接收包规则ActionRecv,nil,[]*regexp.Regexp{regexp.MustCompile(`MBr\x00\x00\x00\x00\x88\x01@\x00`),},},},}// ms-wbt-serverserviceRules["ms-wbt-server"] = serviceRule{Tls: false,DataGroup: []ruleData{{ActionSend,[]byte("\x03\x00\x00*%\xe0\x00\x00\x00\x00\x00Cookie: mstshash=pcpc\r\n\x01\x00\x08\x00\x03\x00\x00\x00"),nil,},{ActionRecv,nil,[]*regexp.Regexp{regexp.MustCompile(`\x03\x00\x00.\x0e\xd0\x00\x00\x124\x00`),},},},}// jdwpserviceRules["jdwp"] = serviceRule{Tls: false,DataGroup: []ruleData{{ActionRecv,[]byte("JDWP-Handshake"),nil,},},}// jdbcserviceRules["jdbc"] = serviceRule{Tls: false,DataGroup: []ruleData{{ActionRecv,[]byte("HSQLDB JDBC Network Listener"),nil,},},}// Db// mysqlserviceRules["mysql"] = serviceRule{Tls: false,DataGroup: []ruleData{{ActionRecv,nil,[]*regexp.Regexp{regexp.MustCompile(`(?s)^.\x00\x00\x00\xff..Host .* is not allowed to connect to this .* server$`),regexp.MustCompile(`^.\x00\x00\x00\xff..Too many connections`),regexp.MustCompile(`(?s)^.\x00\x00\x00\xff..Host .* is blocked because of many connection errors`),regexp.MustCompile(`(?s)^.\x00\x00\x00\x0a(\d\.[-_~.+:\w]+MariaDB-[-_~.+:\w]+)`),regexp.MustCompile(`(?s)^.\x00\x00\x00\x0a(\d\.[-_~.+\w]+)\x00`),regexp.MustCompile(`(?s)^.\x00\x00\x00\xffj\x04'[\d.]+' .* MySQL`),},},},}// redisserviceRules["redis"] = serviceRule{Tls: false,DataGroup: []ruleData{{ActionSend,[]byte("GET / HTTP/1.1\r\n"),nil,},{ActionRecv,nil,[]*regexp.Regexp{regexp.MustCompile(`-ERR operation not permitted\r\n`),regexp.MustCompile(`-ERR wrong number of arguments for 'get' command\r\n`),},},},}// sqlserverserviceRules["sqlserver"] = serviceRule{Tls: false,DataGroup: []ruleData{{ActionSend,[]byte("\x12\x01\x00\x34\x00\x00\x00\x00\x00\x00\x15\x00\x06\x01\x00\x1b\x00\x01\x02\x00\x1c\x00\x0c\x03\x00\x28\x00\x04\xff\x08\x00\x01\x55\x00\x00\x00\x4d\x53\x53\x51\x4c\x53\x65\x72\x76\x65\x72\x00\x48\x0f\x00\x00"),nil,},{ActionRecv,[]byte("\x04\x01\x00\x25\x00\x00\x01\x00\x00\x00\x15\x00\x06\x01\x00\x1b\x00\x01\x02\x00\x1c\x00\x01\x03\x00\x1d\x00\x00\xff"),nil,},},}// oracleserviceRules["oracle"] = serviceRule{Tls: false,DataGroup: []ruleData{{ActionSend,[]byte("\x00Z\x00\x00\x01\x00\x00\x00\x016\x01,\x00\x00\x08\x00\x7F\xFF\x7F\x08\x00\x00\x00\x01\x00 \x00:\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x004\xE6\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00(CONNECT_DATA=(COMMAND=version))"),nil,},{ActionRecv,nil,[]*regexp.Regexp{regexp.MustCompile(`(?s)^\x00\x20\x00\x00\x02\x00\x00\x00\x016\x00\x00\x08\x00\x7f\xff\x01\x00\x00\x00\x00\x20`),regexp.MustCompile(`^\+\x00\x00\x00$`),regexp.MustCompile(`^\x00.\x00\x00\x02\x00\x00\x00.*\(IAGENT`),regexp.MustCompile(`^..\x00\x00\x04\x00\x00\x00"\x00..\(DESCRIPTION=`),regexp.MustCompile(`^\x00.\x00\x00[\x02\x04]\x00\x00\x00.*\(`),regexp.MustCompile(`^\x00.\x00\x00[\x02\x04]\x00\x00\x00.*TNSLSNR`),regexp.MustCompile(`^\x00,\x00\x00\x04\x00\x00"`),},},},}// mongodbserviceRules["mongodb"] = serviceRule{Tls: false,DataGroup: []ruleData{{ActionSend,[]byte("\x41\x00\x00\x00\x3a\x30\x00\x00\xff\xff\xff\xff\xd4\x07\x00\x00\x00\x00\x00\x00test.$cmd\x00\x00\x00\x00\x00\xff\xff\xff\xff\x1b\x00\x00\x00\x01serverStatus\x00\x00\x00\x00\x00\x00\x00\xf0\x3f\x00"),nil,},{ActionRecv,nil,[]*regexp.Regexp{regexp.MustCompile(`(?s)^.*version([: "]+)([.\d]+)"`),regexp.MustCompile(`(?s)^\xcb\x00\x00\x00....:0\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\xa7\x00\x00\x00\x01uptime\x00\x00\x00\x00\x00\x00 ` + "`" + `@\x03globalLock\x009\x00\x00\x00\x01totalTime\x00\x00\x00\x00\x7c\xf0\x9a\x9eA\x01lockTime\x00\x00\x00\x00\x00\x00\xac\x9e@\x01ratio\x00!\xc6\$G\xeb\x08\xf0>\x00\x03mem\x00<\x00\x00\x00\x10resident\x00\x03\x00\x00\x00\x10virtual\x00\xa2\x00\x00\x00\x08supported\x00\x01\x12mapped\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01ok\x00\x00\x00\x00\x00\x00\x00\xf0\?\x00$`),regexp.MustCompile(`(?s)^.\x00\x00\x00....:0\x00\x00\x01\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\+\x00\x00\x00\x02errmsg\x00\x0e\x00\x00\x00need to login\x00\x01ok\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00`),regexp.MustCompile(`(?s)^.\x00\x00\x00....:0\x00\x00\x01\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00.\x00\x00\x00\x01ok\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02errmsg\x00.\x00\x00\x00not authorized on`),},},},}// memcachedserviceRules["memcached"] = serviceRule{Tls: false,DataGroup: []ruleData{{ActionSend,[]byte("stats\\n"),nil,},{ActionRecv,nil,[]*regexp.Regexp{regexp.MustCompile(`(?s)^STAT pid \d`),regexp.MustCompile(`(?s)^ERROR\r\n`),},},},}// onlyRecv 仅接收数据的服务列表for k, m := range serviceRules {if len(m.DataGroup) == 1 {onlyRecv = append(onlyRecv, k)}}
}

 ps:总体来说,这个代码段通过发送特定数据并检查返回数据中是否包含特定标识来识别不同服务的存在。如果匹配成功,则确定该服务在特定端口上运行。

4、core-port-fingerprint-title.go

此文件主要处理从 HTML 文档中提取标题以及获取页面重定向的目标 URL。

首先:定义变量,设置获取title、contenttype、refresh、replace的正则

var (cutset        = "\n\t\v\f\r"reTitle       = regexp.MustCompile(`(?im)<\s*title.*>(.*?)<\s*/\s*title>`)reContentType = regexp.MustCompile(`(?im)\s*charset="(.*?)"|charset=(.*?)"\s*`)reRefresh     = regexp.MustCompile(`(?im)\s*content=['"]\d;url=['"](.*?)['"]`)reReplace     = regexp.MustCompile(`(?im)window\.location\.replace\(['"](.*?)['"]\)`)
)
  • func ExtractTitle(body []byte) (title string)

这个函数用于从 HTML 页面中提取标题。它首先尝试使用 DOM 解析器解析文档,并提取其中的标题。如果解析出错,就使用正则表达式 reTitle 在 HTML 文档中查找标题标签,提取标题内容。最后对提取到的标题进行处理,去除空白字符和特殊符号

func ExtractTitle(body []byte) (title string) {//先从dom中获取titleDom, err := getTitleWithDom(body)//从dom节点中获取title//如果出错,则回退到用正则匹配if  err != nil {for _, match := range reTitle.FindAllString(string(body), -1) {title = matchbreak}}else {title = renderNode(titleDom)//将html转换为字符串类型}title = html.UnescapeString(trimTitleTags(title))//删除多余字符title = strings.TrimSpace(strings.Trim(title, cutset)) //去掉首尾的特殊字符title = strings.ReplaceAll(title, "\n", "")title = strings.ReplaceAll(title, "\r", "")return title
}

  • func getTitleWithDom(body []byte) (*html.Node, error)

这个函数尝试使用 HTML 解析器来解析 HTML 文档,并找到 <title> 标签对应的节点。它通过递归方式遍历 HTML 文档的节点树,在遍历过程中寻找 title 节点

func getTitleWithDom(body []byte) (*html.Node, error){var title *html.Nodevar crawler func(*html.Node)crawler = func(node *html.Node) {//用于遍历 HTML 文档的节点树,查找 <title> 节点。if node.Type == html.ElementNode && node.Data == "title" {title = nodereturn}for child := node.FirstChild; child != nil && title == nil; child = child.NextSibling {crawler(child)}}htmlDoc, err := html.Parse(bytes.NewReader(body))//使用 html.Parse 函数解析传入的 HTML 文档,将其转换为一个 HTML 文档对象(htmlDoc)if err != nil {return nil, err}crawler(htmlDoc)if title != nil {return title, nil}return nil, fmt.Errorf("title not found")}

  • func renderNode(n *html.Node) string

这个函数将 HTML 节点转换为字符串形式,使用 html.Render 将节点内容写入缓冲区,然后将缓冲区的内容以字符串形式返回。

func renderNode(n *html.Node) string{var buf bytes.Bufferw := io.Writer(&buf)html.Render(w, n) //nolintreturn buf.String()
}
  • func trimTitleTags(title string) string

这个函数用于处理提取到的标题字符串,去除 <title> 标签的开始和结束标记,只返回标题文本内容

func trimTitleTags(title string) string {titleBegin := strings.Index(title, ">")titleEnd := strings.Index(title,"</")if titleEnd < 0 || titleBegin < 0 {return title}return title[titleBegin + 1 : titleEnd]
}

  • func GetLocation(body []byte) (location string)

这个函数用于从 HTML 文档中获取重定向的目标 URL。它首先使用正则表达式 reRefresh 在文档中查找重定向的目标 URL,如果未找到,则使用正则表达式 reReplace 进行查找。如果找到,就返回第一个匹配到的重定向 URL。

func GetLocation(body []byte) (location string) {for _, match := range reRefresh.FindAllStringSubmatch(string(body),1){location = match[1]break}if location == "" {for _, match := range reReplace.FindAllStringSubmatch(string(body),1){location = match[1]break}}return
}

 ps:这些函数主要用于解析 HTML 文档,从中提取标题并检索重定向目标 URL。它们结合使用 DOM 解析器和正则表达式来从文档中提取所需的信息。

5、core-port-fingerprint-http.go

这个文件主要设置HTTP客户端和定义功能来处理响应体,识别其编码并限制最大读取大小。包含函数如下:

首先定义:

var ErrOverflow = errors.New("OverflowMax")type Options struct {
}
  • func newHttpClient(dialTimeout time.Duration) *http.Client
  • 创建并配置了一个具有特定传输配置的HTTP客户端,包括TLS设置、超时、最大连接数和禁用长连接等。
  • 设置了一个自定义的 CheckRedirect 函数来限制最大重定向次数为两次。
func newHttpClient(dialTimeout time.Duration) *http.Client {transport := &http.Transport{TLSClientConfig: &tls.Config{//设置httpclient配置InsecureSkipVerify: true,MinVersion: tls.VersionTLS10,},DialContext: (&net.Dialer{Timeout: dialTimeout,}).DialContext,MaxIdleConnsPerHost: 1,IdleConnTimeout: 100 * time.Millisecond,TLSHandshakeTimeout: 3 * time.Second,ExpectContinueTimeout: 3 * time.Second,DisableKeepAlives: true,ForceAttemptHttp2: false,Proxy: http.ProxyFromEnvironment,}// proxy//if options.ProxyUrl != "" {//	proxyUrl, err := url.Parse(options.ProxyUrl)//	if err != nil {//		log.Fatalln(err)//	}//	transport.Proxy = http.ProxyURL(proxyUrl)//}return &http.Client{Timeout : 3 * time.Second,Transport: transport,CheckRedirect: func(req *http.Request, via []*http.Request) error{if len(via) >= 2{return errors.New("stopped after 2 redirects")}return nil},}
}

  • func getBody(resp *http.Response) (body []byte, err error)
  • 根据响应头中的 Content-Encoding 字段确定响应体的编码格式。
  • 通过使用适当的读取器(例如 gzip.NewReaderflate.NewReader)处理不同的压缩格式,如 gzip 和 deflate。
  • 通过调用 readMaxSize 函数,限制响应体的最大大小为300KB。
// getBody 识别响应Body的编码,读取body数据
func getBody(resp *http.Response) (body []byte, err error) {if resp.Body == nil || resp.Body == http.NoBody {return}var reader io.Readerswitch resp.Header.Get("Content-Encoding") {case "gzip":reader, err = gzip.NewReader(resp.Body)case "deflate":reader = flate.NewReader(resp.Body)//case "br"://	reader = brotli.NewReader(resp.Body)default:reader = resp.Body}if err == nil {body, err = readMaxSize(reader, 300*1024) // Max Size 300kb}return
}

  • func readMaxSize(r io.Reader, maxsize int) ([]byte, error)
  • io.Readerr)读取数据,同时限制最大尺寸(maxsize)。
  • 为一个字节切片(b)分配内存,并以块的方式读取数据,将其附加到切片中,直到达到最大尺寸。
  • 如果达到了尺寸限制,函数返回到目前为止读取的数据以及一个 ErrOverflow 错误。

//这个函数的作用是安全地从 io.Reader 中读取数据,并在达到指定的最大尺寸时停止读取,以避免读取过多的数据造成内存溢出或其他问题。
func readMaxSize(r io.Reader, maxsize int) ([]byte, error) {b := make([]byte, 0, 512)for {if len(b) >= maxsize {return b, ErrOverflow}if len(b) == cap(b) {// Add more capacity (let append pick how much).b = append(b, 0)[:len(b)]}n, err := r.Read(b[len(b):cap(b)])b = b[:len(b)+n]if err != nil {if err == io.EOF {err = nil}return b, err}}
}

ps:这些函数为主要目的是创建一个具有特定配置的HTTP客户端,并提供功能来处理响应体的解码,考虑到不同的压缩格式,并限制读取数据的最大尺寸,以防止潜在的过大响应引发问题。

6、core-port-fingerprint-httpInfo.go

这个文件主要实现了HTTP服务信息的探测和提取。主要函数如下:

先定义变量httpsTopPort,高频的https端口

var httpsTopPort = []uint16{443, 4443, 1443, 8443}var httpClient *http.Client
  • func ProbeHttpInfo(ip net.IP, _port uint16, dialTimeout time.Duration) (httpInfo *port.HttpInfo, isDailErr bool)
  • 接收一个 IP、端口和请求超时时间,用于在给定的 IP 地址和端口上探测 HTTP 服务信息。
  • 根据指定的端口列表确定探测的协议(HTTP 或 HTTPS)。
  • 使用 httpClient 对象发送 HTTP 请求,并根据响应提取信息。
  • 通过循环尝试 HTTP 和 HTTPS 请求,根据响应来获取信息,包括状态码、重定向 URL、服务器信息、标题、TLS 证书相关信息等。
  • 通过 webfinger 包来分析响应的指纹信息,还会尝试提取页面的 favicon。
func ProbeHttpInfo(ip net.IP, _port uint16, dialTimeout time.Duration) (httpInfo *port.HttpInfo, isDailErr bool) {if httpClient == nil {httpClient = newHttpClient(dialTimeout)}var err errorvar rewriteUrl stringvar body []bytevar resp *http.Responsevar schemes []stringif util.IsUint16InList(_port, httpsTopPort) { //检测请求的_port是否在httpsTopPort列表中schemes = []string{"https", "http"}}else {schemes = []string["http", "https"}}for _, scheme := range schemes { //循环使用https和http去请求var rewriteNum inturl2 := fmt.Sprintf("%s://%s:%d/", scheme, ip.String(), _port)goReq:resp, body, err = getReq(url2) //获取响应包和响应bodyif err != nil {if strings.HasSuffix(err.Error(), ioTimeoutStr) || strings.Contains(err.Error(), regusedStr) {return nil, true}continue}if resp != nil {if resp.ContentLength == -1 {resp.ContentLength = int64(len(body))}//先在响应头中获取重定向urlrewriteUrl2, _ := resp.Location() //重新获取url,提取响应包头中的location参数值if rewriteUrl2 != nil {rewriteUrl = rewriteUrl2.String()}else {rewriteUrl = ""}//其次在body中获取重定向urllocation := GetLocation(body) //在title.go中,用于获取body中的location 的url,即重定向urlif rewriteUrl == "" && location != "" {rewriteUrl = location}if location != "" && rewriteNum < 3 {if !strings.HasPrefix(location, "http") { //判断location前缀中是否http开头,如果不是,则进入下一个ifif  strings.HasPrefix(location, "/") {resp.Request.URL.Path = location}else {resp.Request.URL.Path = resp.Request.URL.Path[:strings.LastIndex(resp.Request.URL.Path, "/")+1] + location//将 HTTP 请求的 URL 路径修改为原路径中最后一个斜杠之前的部分,再添加上 location 变量的值。这样,新的路径将以最后一个斜杠结尾,然后连接上 location 的值。}location = resp.Request.URL.String()}url2 = locationrewriteNum++goto goReq}httpInfo = new(port.HttpInfo) //在port.go文件中,为HttpInfo结构体httpInfo.Url = resp.Request.URL.String()httpInfo.StatusCode = resp.StatusCodehttpInfo.ContentLen = int(resp.ContentLength)httpInfo.Location = rewriteUrlhttpInfo.Server = resp.Header.Get("Server")httpInfo.Title = ExtracTitle(body) //title.go文件中,获取响应包中的titleif resp.TLS != nil && len(resp.TLS.PeerCertificates) > 0 { //如果是https,获取tls握手证书的信息httpInfo.TlsCN = resp.TLS.PeerCertificates[0].Subject.CommonName //证书主题部分的通用名称信息httpInfo.TlsDNS = resp.TLS.PeerCertificates[0].DNSNames //证书中包含的DNS名称列表}//指纹信息err = webfinger.ParseWebFingerData(webfinger.DefFingerData)if err == nil {resp.Body = io.NopCloser(bytes.NewReader(body))httpInfo.Fingers = webfinger.WebFingerIdent(resp) //j检测识别响应头和响应body识别指纹//faviconfau := webfinger.FindFaviconUrl(string(body)) //获取响应body中的favionUrlif fau != ""{if !strings.HasPrefix(fau, "http") {fau = resp.Request.URL.String() + fau}_, body2, err2 := getReq(fau) //获取请求favicon的响应包信息if err2 == nil && len(body2) != 0{httpInfo.Fingers = append(httpInfo.Fingers, webfinger.WebFingerIdentByFavicon(body2)...)//检测favicon指纹}}}if resp.StatusCode != 400 {break}			}}return httpInfo, false}

  • func getReq(url2 string) (resp *http.Response, body []byte, err error)
  • 构造一个 HTTP GET 请求,设置请求头,然后使用 httpClient 执行请求。
  • 从响应中读取内容,如果是文本类型则进行编码解析(尝试将非 UTF-8 编码的文本解析为 UTF-8)。
func getReq (url2 string) (resp *http.Response, body []byte, err error) {req, err := http.NewRequest(http.MethodGet, url2, http.Nobody) //创建get请求if err != nil {return}req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36")req.Header.Set("Accept-Encoding", "gzip, deflate")req.Close = true //关闭keepaliveresp, err = httpClient.Do(req)if err != nil {return}if resp.Body != http.NoBody && resp.Body != nil { //如果body不为空且不为http.NoBody,则提取bodybody, _ = getBody(resp) //在http.go文件中,主要为识别body的编码,读取body数据if contentTypes, _ := resp.Header["Content-Type"]; len(contentTypes) > 0 {if strings.Contains(contentTypes[0], "text") {//如果响应类型是文本类型,则对响应体进行解码,将非utf-8编码的文本解析为utf-8_body, err2 := DecodeData(body, resp.Header)if err2 == nil {body = _body}resp.Body = io.NopCloser(bytes.NewReader(body))}}}return}

ps:这些函数主要是尝试使用 HTTP 和 HTTPS 进行请求,根据响应提取 HTTP 服务的相关信息,包括标题、服务器信息、TLS 证书信息以及其他可能的指纹信息,为后续服务识别和指纹探测提供数据支持。

四、小结

这部分的功能主要围绕在对http和https指纹的识别,通过网页titile和icon的hash进行指纹判断;其次是针对端口进行服务识别,主要匹配规则库在“fingerprint-webfinger-finger.json” 文件和“core-port-fingerprint-rules.go”文件中。

这篇关于分析某款go端口扫描器之二的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/492685

相关文章

性能分析之MySQL索引实战案例

文章目录 一、前言二、准备三、MySQL索引优化四、MySQL 索引知识回顾五、总结 一、前言 在上一讲性能工具之 JProfiler 简单登录案例分析实战中已经发现SQL没有建立索引问题,本文将一起从代码层去分析为什么没有建立索引? 开源ERP项目地址:https://gitee.com/jishenghua/JSH_ERP 二、准备 打开IDEA找到登录请求资源路径位置

SWAP作物生长模型安装教程、数据制备、敏感性分析、气候变化影响、R模型敏感性分析与贝叶斯优化、Fortran源代码分析、气候数据降尺度与变化影响分析

查看原文>>>全流程SWAP农业模型数据制备、敏感性分析及气候变化影响实践技术应用 SWAP模型是由荷兰瓦赫宁根大学开发的先进农作物模型,它综合考虑了土壤-水分-大气以及植被间的相互作用;是一种描述作物生长过程的一种机理性作物生长模型。它不但运用Richard方程,使其能够精确的模拟土壤中水分的运动,而且耦合了WOFOST作物模型使作物的生长描述更为科学。 本文让更多的科研人员和农业工作者

MOLE 2.5 分析分子通道和孔隙

软件介绍 生物大分子通道和孔隙在生物学中发挥着重要作用,例如在分子识别和酶底物特异性方面。 我们介绍了一种名为 MOLE 2.5 的高级软件工具,该工具旨在分析分子通道和孔隙。 与其他可用软件工具的基准测试表明,MOLE 2.5 相比更快、更强大、功能更丰富。作为一项新功能,MOLE 2.5 可以估算已识别通道的物理化学性质。 软件下载 https://pan.quark.cn/s/57

衡石分析平台使用手册-单机安装及启动

单机安装及启动​ 本文讲述如何在单机环境下进行 HENGSHI SENSE 安装的操作过程。 在安装前请确认网络环境,如果是隔离环境,无法连接互联网时,请先按照 离线环境安装依赖的指导进行依赖包的安装,然后按照本文的指导继续操作。如果网络环境可以连接互联网,请直接按照本文的指导进行安装。 准备工作​ 请参考安装环境文档准备安装环境。 配置用户与安装目录。 在操作前请检查您是否有 sud

线性因子模型 - 独立分量分析(ICA)篇

序言 线性因子模型是数据分析与机器学习中的一类重要模型,它们通过引入潜变量( latent variables \text{latent variables} latent variables)来更好地表征数据。其中,独立分量分析( ICA \text{ICA} ICA)作为线性因子模型的一种,以其独特的视角和广泛的应用领域而备受关注。 ICA \text{ICA} ICA旨在将观察到的复杂信号

Go Playground 在线编程环境

For all examples in this and the next chapter, we will use Go Playground. Go Playground represents a web service that can run programs written in Go. It can be opened in a web browser using the follow

如何编写Linux PCIe设备驱动器 之二

如何编写Linux PCIe设备驱动器 之二 功能(capability)集功能(capability)APIs通过pci_bus_read_config完成功能存取功能APIs参数pos常量值PCI功能结构 PCI功能IDMSI功能电源功率管理功能 功能(capability)集 功能(capability)APIs int pcie_capability_read_wo

go基础知识归纳总结

无缓冲的 channel 和有缓冲的 channel 的区别? 在 Go 语言中,channel 是用来在 goroutines 之间传递数据的主要机制。它们有两种类型:无缓冲的 channel 和有缓冲的 channel。 无缓冲的 channel 行为:无缓冲的 channel 是一种同步的通信方式,发送和接收必须同时发生。如果一个 goroutine 试图通过无缓冲 channel

如何确定 Go 语言中 HTTP 连接池的最佳参数?

确定 Go 语言中 HTTP 连接池的最佳参数可以通过以下几种方式: 一、分析应用场景和需求 并发请求量: 确定应用程序在特定时间段内可能同时发起的 HTTP 请求数量。如果并发请求量很高,需要设置较大的连接池参数以满足需求。例如,对于一个高并发的 Web 服务,可能同时有数百个请求在处理,此时需要较大的连接池大小。可以通过压力测试工具模拟高并发场景,观察系统在不同并发请求下的性能表现,从而

【软考】希尔排序算法分析

目录 1. c代码2. 运行截图3. 运行解析 1. c代码 #include <stdio.h>#include <stdlib.h> void shellSort(int data[], int n){// 划分的数组,例如8个数则为[4, 2, 1]int *delta;int k;// i控制delta的轮次int i;// 临时变量,换值int temp;in