mirror of
https://github.com/SleepingBag945/dddd.git
synced 2025-06-08 14:06:33 +00:00
385 lines
9.2 KiB
Go
Executable File
385 lines
9.2 KiB
Go
Executable File
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
|
||
}
|