2023-08-18 08:55:46 +02:00
|
|
|
|
package gonmap
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
|
|
|
|
"fmt"
|
|
|
|
|
"github.com/miekg/dns"
|
|
|
|
|
"strings"
|
|
|
|
|
"time"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type Nmap struct {
|
|
|
|
|
exclude PortList
|
|
|
|
|
portProbeMap map[int]ProbeList
|
|
|
|
|
probeNameMap map[string]*probe
|
|
|
|
|
probeSort ProbeList
|
|
|
|
|
|
|
|
|
|
probeUsed ProbeList
|
|
|
|
|
|
|
|
|
|
filter int
|
|
|
|
|
|
|
|
|
|
//检测端口存活的超时时间
|
|
|
|
|
timeout time.Duration
|
|
|
|
|
|
|
|
|
|
bypassAllProbePort PortList
|
|
|
|
|
sslSecondProbeMap ProbeList
|
|
|
|
|
allProbeMap ProbeList
|
|
|
|
|
sslProbeMap ProbeList
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (n *Nmap) ScanTimeout(ip string, port int, timeout time.Duration) (status Status, response *Response) {
|
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
|
|
|
|
var resChan = make(chan bool)
|
|
|
|
|
|
|
|
|
|
defer func() {
|
|
|
|
|
close(resChan)
|
|
|
|
|
cancel()
|
|
|
|
|
}()
|
|
|
|
|
|
|
|
|
|
go func() {
|
|
|
|
|
defer func() {
|
|
|
|
|
if r := recover(); r != nil {
|
|
|
|
|
if fmt.Sprint(r) != "send on closed channel" {
|
|
|
|
|
panic(r)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}()
|
|
|
|
|
status, response = n.Scan(ip, port)
|
|
|
|
|
resChan <- true
|
|
|
|
|
}()
|
|
|
|
|
|
|
|
|
|
select {
|
|
|
|
|
case <-ctx.Done():
|
|
|
|
|
return Closed, nil
|
|
|
|
|
case <-resChan:
|
|
|
|
|
return status, response
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (n *Nmap) Scan(ip string, port int) (status Status, response *Response) {
|
|
|
|
|
var probeNames ProbeList
|
|
|
|
|
if n.bypassAllProbePort.exist(port) == true {
|
|
|
|
|
probeNames = append(n.portProbeMap[port], n.allProbeMap...)
|
|
|
|
|
} else {
|
|
|
|
|
probeNames = append(n.allProbeMap, n.portProbeMap[port]...)
|
|
|
|
|
}
|
|
|
|
|
probeNames = append(probeNames, n.sslProbeMap...)
|
|
|
|
|
|
|
|
|
|
//探针去重
|
|
|
|
|
probeNames = probeNames.removeDuplicate()
|
|
|
|
|
|
|
|
|
|
firstProbe := probeNames[0]
|
|
|
|
|
status, response = n.getRealResponse(ip, port, n.timeout, firstProbe)
|
|
|
|
|
if status == Closed || status == Matched {
|
|
|
|
|
return status, response
|
|
|
|
|
}
|
|
|
|
|
otherProbes := probeNames[1:]
|
2024-04-03 06:32:26 +02:00
|
|
|
|
status, response = n.getRealResponse(ip, port, n.timeout, otherProbes...)
|
|
|
|
|
return status, response
|
2023-08-18 08:55:46 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (n *Nmap) getRealResponse(host string, port int, timeout time.Duration, probes ...string) (status Status, response *Response) {
|
2024-04-03 06:32:26 +02:00
|
|
|
|
status, response = n.getResponseByProbes(host, port, timeout, probes)
|
2023-08-18 08:55:46 +02:00
|
|
|
|
if status != Matched {
|
|
|
|
|
return status, response
|
|
|
|
|
}
|
|
|
|
|
if response.FingerPrint.Service == "ssl" {
|
|
|
|
|
status, response := n.getResponseBySSLSecondProbes(host, port, timeout)
|
|
|
|
|
if status == Matched {
|
|
|
|
|
return Matched, response
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return status, response
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (n *Nmap) getResponseBySSLSecondProbes(host string, port int, timeout time.Duration) (status Status, response *Response) {
|
2024-04-03 06:32:26 +02:00
|
|
|
|
status, response = n.getResponseByProbes(host, port, timeout, n.sslSecondProbeMap)
|
2023-08-18 08:55:46 +02:00
|
|
|
|
if status != Matched || response.FingerPrint.Service == "ssl" {
|
|
|
|
|
status, response = n.getResponseByHTTPS(host, port, timeout)
|
|
|
|
|
}
|
|
|
|
|
if status == Matched && response.FingerPrint.Service != "ssl" {
|
|
|
|
|
if response.FingerPrint.Service == "http" {
|
|
|
|
|
response.FingerPrint.Service = "https"
|
|
|
|
|
}
|
|
|
|
|
return Matched, response
|
|
|
|
|
}
|
|
|
|
|
return NotMatched, response
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (n *Nmap) getResponseByHTTPS(host string, port int, timeout time.Duration) (status Status, response *Response) {
|
|
|
|
|
var httpRequest = n.probeNameMap["TCP_GetRequest"]
|
|
|
|
|
return n.getResponse(host, port, true, timeout, httpRequest)
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-03 06:32:26 +02:00
|
|
|
|
func (n *Nmap) getResponseByProbes(host string, port int, timeout time.Duration, probes []string) (status Status, response *Response) {
|
2023-08-18 08:55:46 +02:00
|
|
|
|
var responseNotMatch *Response
|
|
|
|
|
for _, requestName := range probes {
|
|
|
|
|
if n.probeUsed.exist(requestName) {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
n.probeUsed = append(n.probeUsed, requestName)
|
|
|
|
|
p := n.probeNameMap[requestName]
|
|
|
|
|
|
|
|
|
|
status, response = n.getResponse(host, port, p.sslports.exist(port), timeout, p)
|
|
|
|
|
//如果端口未开放,则等待10s后重新连接
|
|
|
|
|
//if b.status == Closed {
|
|
|
|
|
// time.Sleep(time.Second * 10)
|
|
|
|
|
// b.Load(n.getResponse(host, port, n.probeNameMap[requestName]))
|
|
|
|
|
//}
|
|
|
|
|
|
|
|
|
|
//logger.Printf("Target:%s:%d,Probe:%s,Status:%v", host, port, requestName, status)
|
|
|
|
|
|
|
|
|
|
if status == Closed || status == Matched {
|
|
|
|
|
responseNotMatch = nil
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
if status == NotMatched {
|
|
|
|
|
responseNotMatch = response
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if responseNotMatch != nil {
|
|
|
|
|
response = responseNotMatch
|
|
|
|
|
}
|
|
|
|
|
return status, response
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (n *Nmap) getResponse(host string, port int, tls bool, timeout time.Duration, p *probe) (Status, *Response) {
|
|
|
|
|
if port == 53 {
|
|
|
|
|
if DnsScan(host, port) {
|
|
|
|
|
return Matched, &dnsResponse
|
|
|
|
|
} else {
|
|
|
|
|
return Closed, nil
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
text, tls, err := p.scan(host, port, tls, timeout, 10240)
|
|
|
|
|
if err != nil {
|
|
|
|
|
if strings.Contains(err.Error(), "STEP1") {
|
|
|
|
|
return Closed, nil
|
|
|
|
|
}
|
|
|
|
|
if strings.Contains(err.Error(), "STEP2") {
|
|
|
|
|
return Closed, nil
|
|
|
|
|
}
|
|
|
|
|
if p.protocol == "UDP" && strings.Contains(err.Error(), "refused") {
|
|
|
|
|
return Closed, nil
|
|
|
|
|
}
|
|
|
|
|
return Open, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
response := &Response{
|
|
|
|
|
Raw: text,
|
|
|
|
|
TLS: tls,
|
|
|
|
|
FingerPrint: &FingerPrint{},
|
|
|
|
|
}
|
|
|
|
|
//若存在返回包,则开始捕获指纹
|
|
|
|
|
fingerPrint := n.getFinger(text, tls, p.name)
|
|
|
|
|
response.FingerPrint = fingerPrint
|
|
|
|
|
|
|
|
|
|
if fingerPrint.Service == "" {
|
|
|
|
|
return NotMatched, response
|
|
|
|
|
} else {
|
|
|
|
|
return Matched, response
|
|
|
|
|
}
|
|
|
|
|
//如果成功匹配指纹,则直接返回指纹
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (n *Nmap) getFinger(responseRaw string, tls bool, requestName string) *FingerPrint {
|
|
|
|
|
data := n.convResponse(responseRaw)
|
|
|
|
|
probe := n.probeNameMap[requestName]
|
|
|
|
|
|
|
|
|
|
finger := probe.match(data)
|
|
|
|
|
|
2024-04-03 06:32:26 +02:00
|
|
|
|
// 沙雕正则匹配adb失败,暴力判断
|
|
|
|
|
if requestName == "TCP_adbConnect" && finger.Service == "" {
|
|
|
|
|
if (strings.HasPrefix(responseRaw, "CNXN\x01\x00\x00\x01\x00\x10\x00") || strings.HasPrefix(responseRaw, "AUTH\x01\x00\x00\x01\x00\x10\x00")) &&
|
|
|
|
|
strings.Contains(responseRaw, "\xbc\xb1\xa7\xb1") {
|
|
|
|
|
return &FingerPrint{
|
|
|
|
|
ProbeName: requestName,
|
|
|
|
|
Service: "adb",
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-08-18 08:55:46 +02:00
|
|
|
|
if tls == true {
|
|
|
|
|
if finger.Service == "http" {
|
|
|
|
|
finger.Service = "https"
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if finger.Service != "" || n.probeNameMap[requestName].fallback == "" {
|
|
|
|
|
//标记当前探针名称
|
|
|
|
|
finger.ProbeName = requestName
|
|
|
|
|
return finger
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fallback := n.probeNameMap[requestName].fallback
|
|
|
|
|
fallbackProbe := n.probeNameMap[fallback]
|
|
|
|
|
for fallback != "" {
|
|
|
|
|
// logger.Println(requestName, " fallback is :", fallback)
|
|
|
|
|
finger = fallbackProbe.match(data)
|
|
|
|
|
fallback = n.probeNameMap[fallback].fallback
|
|
|
|
|
if finger.Service != "" {
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
//标记当前探针名称
|
|
|
|
|
finger.ProbeName = requestName
|
|
|
|
|
return finger
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (n *Nmap) convResponse(s1 string) string {
|
|
|
|
|
//为了适配go语言的沙雕正则,只能讲二进制强行转换成UTF-8
|
|
|
|
|
b1 := []byte(s1)
|
|
|
|
|
var r1 []rune
|
|
|
|
|
for _, i := range b1 {
|
|
|
|
|
r1 = append(r1, rune(i))
|
|
|
|
|
}
|
|
|
|
|
s2 := string(r1)
|
|
|
|
|
return s2
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//配置类
|
|
|
|
|
|
|
|
|
|
func (n *Nmap) SetTimeout(timeout time.Duration) {
|
|
|
|
|
n.timeout = timeout
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (n *Nmap) OpenDeepIdentify() {
|
|
|
|
|
//-sV参数深度解析
|
|
|
|
|
n.allProbeMap = n.probeSort
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (n *Nmap) AddMatch(probeName string, expr string) {
|
|
|
|
|
var probe = n.probeNameMap[probeName]
|
|
|
|
|
probe.loadMatch(expr, false)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//初始化类
|
|
|
|
|
|
|
|
|
|
func (n *Nmap) loads(s string) {
|
|
|
|
|
lines := strings.Split(s, "\n")
|
|
|
|
|
var probeGroups [][]string
|
|
|
|
|
var probeLines []string
|
|
|
|
|
for _, line := range lines {
|
|
|
|
|
if !n.isCommand(line) {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
commandName := line[:strings.Index(line, " ")]
|
|
|
|
|
if commandName == "Exclude" {
|
|
|
|
|
n.loadExclude(line)
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
if commandName == "Probe" {
|
|
|
|
|
if len(probeLines) != 0 {
|
|
|
|
|
probeGroups = append(probeGroups, probeLines)
|
|
|
|
|
probeLines = []string{}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
probeLines = append(probeLines, line)
|
|
|
|
|
}
|
|
|
|
|
probeGroups = append(probeGroups, probeLines)
|
|
|
|
|
|
|
|
|
|
for _, lines := range probeGroups {
|
|
|
|
|
p := parseProbe(lines)
|
|
|
|
|
n.pushProbe(*p)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (n *Nmap) loadExclude(expr string) {
|
|
|
|
|
n.exclude = parsePortList(expr)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (n *Nmap) pushProbe(p probe) {
|
|
|
|
|
n.probeSort = append(n.probeSort, p.name)
|
|
|
|
|
n.probeNameMap[p.name] = &p
|
|
|
|
|
|
|
|
|
|
//建立端口扫描对应表,将根据端口号决定使用何种请求包
|
|
|
|
|
//如果端口列表为空,则为全端口
|
|
|
|
|
if p.rarity > n.filter {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
//0记录所有使用的探针
|
|
|
|
|
n.portProbeMap[0] = append(n.portProbeMap[0], p.name)
|
|
|
|
|
|
|
|
|
|
//分别压入sslports,ports
|
|
|
|
|
for _, i := range p.ports {
|
|
|
|
|
n.portProbeMap[i] = append(n.portProbeMap[i], p.name)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, i := range p.sslports {
|
|
|
|
|
n.portProbeMap[i] = append(n.portProbeMap[i], p.name)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (n *Nmap) fixFallback() {
|
|
|
|
|
for probeName, probeType := range n.probeNameMap {
|
|
|
|
|
fallback := probeType.fallback
|
|
|
|
|
if fallback == "" {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
if _, ok := n.probeNameMap["TCP_"+fallback]; ok {
|
|
|
|
|
n.probeNameMap[probeName].fallback = "TCP_" + fallback
|
|
|
|
|
} else {
|
|
|
|
|
n.probeNameMap[probeName].fallback = "UDP_" + fallback
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (n *Nmap) isCommand(line string) bool {
|
|
|
|
|
//删除注释行和空行
|
|
|
|
|
if len(line) < 2 {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
if line[:1] == "#" {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
//删除异常命令
|
|
|
|
|
commandName := line[:strings.Index(line, " ")]
|
|
|
|
|
commandArr := []string{
|
|
|
|
|
"Exclude", "Probe", "match", "softmatch", "ports", "sslports", "totalwaitms", "tcpwrappedms", "rarity", "fallback",
|
|
|
|
|
}
|
|
|
|
|
for _, item := range commandArr {
|
|
|
|
|
if item == commandName {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (n *Nmap) sortOfRarity(list ProbeList) ProbeList {
|
|
|
|
|
if len(list) == 0 {
|
|
|
|
|
return list
|
|
|
|
|
}
|
|
|
|
|
var raritySplice []int
|
|
|
|
|
for _, probeName := range list {
|
|
|
|
|
rarity := n.probeNameMap[probeName].rarity
|
|
|
|
|
raritySplice = append(raritySplice, rarity)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for i := 0; i < len(raritySplice)-1; i++ {
|
|
|
|
|
for j := 0; j < len(raritySplice)-i-1; j++ {
|
|
|
|
|
if raritySplice[j] > raritySplice[j+1] {
|
|
|
|
|
m := raritySplice[j+1]
|
|
|
|
|
raritySplice[j+1] = raritySplice[j]
|
|
|
|
|
raritySplice[j] = m
|
|
|
|
|
mp := list[j+1]
|
|
|
|
|
list[j+1] = list[j]
|
|
|
|
|
list[j] = mp
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, probeName := range list {
|
|
|
|
|
rarity := n.probeNameMap[probeName].rarity
|
|
|
|
|
raritySplice = append(raritySplice, rarity)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return list
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 工具函数
|
|
|
|
|
func DnsScan(host string, port int) bool {
|
|
|
|
|
domainServer := fmt.Sprintf("%s:%d", host, port)
|
|
|
|
|
c := dns.Client{
|
|
|
|
|
Timeout: 2 * time.Second,
|
|
|
|
|
}
|
|
|
|
|
m := dns.Msg{}
|
|
|
|
|
// 最终都会指向一个ip 也就是typeA, 这样就可以返回所有层的cname.
|
|
|
|
|
m.SetQuestion("www.baidu.com.", dns.TypeA)
|
|
|
|
|
_, _, err := c.Exchange(&m, domainServer)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
return true
|
|
|
|
|
}
|