dddd/lib/gonmap/type-nmap.go
SleepingBag945 01e7d0b159 dddd
2023-08-18 08:55:46 +02:00

385 lines
9.2 KiB
Go
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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:]
return n.getRealResponse(ip, port, 3*time.Second, otherProbes...)
}
func (n *Nmap) getRealResponse(host string, port int, timeout time.Duration, probes ...string) (status Status, response *Response) {
status, response = n.getResponseByProbes(host, port, timeout, probes...)
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) {
status, response = n.getResponseByProbes(host, port, timeout, n.sslSecondProbeMap...)
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)
}
func (n *Nmap) getResponseByProbes(host string, port int, timeout time.Duration, probes ...string) (status Status, response *Response) {
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)
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
}