diff --git a/.gitignore b/.gitignore
index 387c605..a6e63a1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,5 @@
result.txt
+main
+.idea
+fscan.exe
+fscan
diff --git a/Common/Config.go b/Common/Config.go
new file mode 100644
index 0000000..8dea029
--- /dev/null
+++ b/Common/Config.go
@@ -0,0 +1,952 @@
+package Common
+
+import (
+ "github.com/schollz/progressbar/v3"
+ "sync"
+)
+
+var version = "2.0.0"
+var Userdict = map[string][]string{
+ "ftp": {"ftp", "admin", "www", "web", "root", "db", "wwwroot", "data"},
+ "mysql": {"root", "mysql"},
+ "mssql": {"sa", "sql"},
+ "smb": {"administrator", "admin", "guest"},
+ "rdp": {"administrator", "admin", "guest"},
+ "postgresql": {"postgres", "admin"},
+ "ssh": {"root", "admin"},
+ "mongodb": {"root", "admin"},
+ "oracle": {"sys", "system", "admin", "test", "web", "orcl"},
+ "telnet": {"root", "admin", "test"},
+ "elastic": {"elastic", "admin", "kibana"},
+ "rabbitmq": {"guest", "admin", "administrator", "rabbit", "rabbitmq", "root"},
+ "kafka": {"admin", "kafka", "root", "test"},
+ "activemq": {"admin", "root", "activemq", "system", "user"},
+ "ldap": {"admin", "administrator", "root", "cn=admin", "cn=administrator", "cn=manager"},
+ "smtp": {"admin", "root", "postmaster", "mail", "smtp", "administrator"},
+ "imap": {"admin", "mail", "postmaster", "root", "user", "test"},
+ "pop3": {"admin", "root", "mail", "user", "test", "postmaster"},
+ "zabbix": {"Admin", "admin", "guest", "user"},
+ "rsync": {"rsync", "root", "admin", "backup"},
+ "cassandra": {"cassandra", "admin", "root", "system"},
+ "neo4j": {"neo4j", "admin", "root", "test"},
+}
+
+var DefaultMap = []string{
+ "GenericLines",
+ "GetRequest",
+ "TLSSessionReq",
+ "SSLSessionReq",
+ "ms-sql-s",
+ "JavaRMI",
+ "LDAPSearchReq",
+ "LDAPBindReq",
+ "oracle-tns",
+ "Socks5",
+}
+
+var PortMap = map[int][]string{
+ 1: {"GetRequest", "Help"},
+ 7: {"Help"},
+ 21: {"GenericLines", "Help"},
+ 23: {"GenericLines", "tn3270"},
+ 25: {"Hello", "Help"},
+ 35: {"GenericLines"},
+ 42: {"SMBProgNeg"},
+ 43: {"GenericLines"},
+ 53: {"DNSVersionBindReqTCP", "DNSStatusRequestTCP"},
+ 70: {"GetRequest"},
+ 79: {"GenericLines", "GetRequest", "Help"},
+ 80: {"GetRequest", "HTTPOptions", "RTSPRequest", "X11Probe", "FourOhFourRequest"},
+ 81: {"GetRequest", "HTTPOptions", "RPCCheck", "FourOhFourRequest"},
+ 82: {"GetRequest", "HTTPOptions", "FourOhFourRequest"},
+ 83: {"GetRequest", "HTTPOptions", "FourOhFourRequest"},
+ 84: {"GetRequest", "HTTPOptions", "FourOhFourRequest"},
+ 85: {"GetRequest", "HTTPOptions", "FourOhFourRequest"},
+ 88: {"GetRequest", "Kerberos", "SMBProgNeg", "FourOhFourRequest"},
+ 98: {"GenericLines"},
+ 110: {"GenericLines"},
+ 111: {"RPCCheck"},
+ 113: {"GenericLines", "GetRequest", "Help"},
+ 119: {"GenericLines", "Help"},
+ 130: {"NotesRPC"},
+ 135: {"DNSVersionBindReqTCP", "SMBProgNeg"},
+ 139: {"GetRequest", "SMBProgNeg"},
+ 143: {"GetRequest"},
+ 175: {"NJE"},
+ 199: {"GenericLines", "RPCCheck", "Socks5", "Socks4"},
+ 214: {"GenericLines"},
+ 256: {"LDAPSearchReq", "LDAPBindReq"},
+ 257: {"LDAPSearchReq", "LDAPBindReq"},
+ 261: {"SSLSessionReq"},
+ 264: {"GenericLines"},
+ 271: {"SSLSessionReq"},
+ 280: {"GetRequest"},
+ 322: {"RTSPRequest", "SSLSessionReq"},
+ 324: {"SSLSessionReq"},
+ 389: {"LDAPSearchReq", "LDAPBindReq"},
+ 390: {"LDAPSearchReq", "LDAPBindReq"},
+ 406: {"SIPOptions"},
+ 427: {"NotesRPC"},
+ 443: {"TLSSessionReq", "GetRequest", "HTTPOptions", "SSLSessionReq", "SSLv23SessionReq", "X11Probe", "FourOhFourRequest", "tor-versions", "OpenVPN"},
+ 444: {"TLSSessionReq", "SSLSessionReq", "SSLv23SessionReq"},
+ 445: {"SMBProgNeg"},
+ 448: {"SSLSessionReq"},
+ 449: {"GenericLines"},
+ 465: {"Hello", "Help", "TLSSessionReq", "SSLSessionReq", "SSLv23SessionReq"},
+ 497: {"GetRequest", "X11Probe"},
+ 500: {"OpenVPN"},
+ 505: {"GenericLines", "GetRequest"},
+ 510: {"GenericLines"},
+ 512: {"DNSVersionBindReqTCP"},
+ 513: {"DNSVersionBindReqTCP", "DNSStatusRequestTCP"},
+ 514: {"GetRequest", "RPCCheck", "DNSVersionBindReqTCP", "DNSStatusRequestTCP"},
+ 515: {"GetRequest", "Help", "LPDString", "TerminalServer"},
+ 523: {"ibm-db2-das", "ibm-db2"},
+ 524: {"NCP"},
+ 540: {"GenericLines", "GetRequest"},
+ 543: {"DNSVersionBindReqTCP"},
+ 544: {"RPCCheck", "DNSVersionBindReqTCP"},
+ 548: {"SSLSessionReq", "SSLv23SessionReq", "afp"},
+ 554: {"GetRequest", "RTSPRequest"},
+ 563: {"SSLSessionReq"},
+ 585: {"SSLSessionReq"},
+ 587: {"GenericLines", "Hello", "Help"},
+ 591: {"GetRequest"},
+ 616: {"GenericLines"},
+ 620: {"GetRequest"},
+ 623: {"tn3270"},
+ 628: {"GenericLines", "DNSVersionBindReqTCP"},
+ 631: {"GetRequest", "HTTPOptions"},
+ 636: {"TLSSessionReq", "SSLSessionReq", "SSLv23SessionReq", "LDAPSearchReq", "LDAPBindReq"},
+ 637: {"LDAPSearchReq", "LDAPBindReq"},
+ 641: {"HTTPOptions"},
+ 660: {"SMBProgNeg"},
+ 666: {"GenericLines", "beast2"},
+ 684: {"SSLSessionReq"},
+ 706: {"JavaRMI", "mydoom", "WWWOFFLEctrlstat"},
+ 710: {"RPCCheck"},
+ 711: {"RPCCheck"},
+ 731: {"GenericLines"},
+ 771: {"GenericLines"},
+ 782: {"GenericLines"},
+ 783: {"GetRequest"},
+ 853: {"DNSVersionBindReqTCP", "DNSStatusRequestTCP", "SSLSessionReq"},
+ 888: {"GetRequest"},
+ 898: {"GetRequest"},
+ 900: {"GetRequest"},
+ 901: {"GetRequest"},
+ 989: {"GenericLines", "TLSSessionReq", "SSLSessionReq", "SSLv23SessionReq"},
+ 990: {"GenericLines", "Help", "TLSSessionReq", "SSLSessionReq", "SSLv23SessionReq"},
+ 992: {"GenericLines", "TLSSessionReq", "SSLSessionReq", "SSLv23SessionReq", "tn3270"},
+ 993: {"GetRequest", "TLSSessionReq", "SSLSessionReq", "SSLv23SessionReq"},
+ 994: {"TLSSessionReq", "SSLSessionReq", "SSLv23SessionReq"},
+ 995: {"GenericLines", "GetRequest", "TLSSessionReq", "SSLSessionReq", "SSLv23SessionReq"},
+ 999: {"JavaRMI"},
+ 1000: {"GenericLines"},
+ 1010: {"GenericLines"},
+ 1025: {"SMBProgNeg"},
+ 1026: {"GetRequest"},
+ 1027: {"SMBProgNeg"},
+ 1028: {"TerminalServer"},
+ 1029: {"DNSVersionBindReqTCP"},
+ 1030: {"JavaRMI"},
+ 1031: {"SMBProgNeg"},
+ 1035: {"JavaRMI", "oracle-tns"},
+ 1040: {"GenericLines"},
+ 1041: {"GenericLines"},
+ 1042: {"GenericLines", "GetRequest"},
+ 1043: {"GenericLines"},
+ 1068: {"TerminalServer"},
+ 1080: {"GenericLines", "GetRequest", "Socks5", "Socks4"},
+ 1090: {"JavaRMI", "Socks5", "Socks4"},
+ 1095: {"Socks5", "Socks4"},
+ 1098: {"JavaRMI"},
+ 1099: {"JavaRMI"},
+ 1100: {"JavaRMI", "Socks5", "Socks4"},
+ 1101: {"JavaRMI"},
+ 1102: {"JavaRMI"},
+ 1103: {"JavaRMI"},
+ 1105: {"Socks5", "Socks4"},
+ 1109: {"Socks5", "Socks4"},
+ 1111: {"Help"},
+ 1112: {"SMBProgNeg"},
+ 1129: {"JavaRMI"},
+ 1194: {"OpenVPN"},
+ 1199: {"JavaRMI"},
+ 1200: {"NCP"},
+ 1212: {"GenericLines"},
+ 1214: {"GetRequest"},
+ 1217: {"NCP"},
+ 1220: {"GenericLines", "GetRequest"},
+ 1234: {"GetRequest", "JavaRMI"},
+ 1241: {"TLSSessionReq", "SSLSessionReq", "SSLv23SessionReq", "NessusTPv12", "NessusTPv12", "NessusTPv11", "NessusTPv11", "NessusTPv10", "NessusTPv10"},
+ 1248: {"GenericLines"},
+ 1302: {"GenericLines"},
+ 1311: {"GetRequest", "Help", "TLSSessionReq", "SSLSessionReq", "SSLv23SessionReq"},
+ 1314: {"GetRequest"},
+ 1344: {"GetRequest"},
+ 1352: {"NotesRPC"},
+ 1400: {"GenericLines"},
+ 1414: {"ibm-mqseries"},
+ 1415: {"ibm-mqseries"},
+ 1416: {"ibm-mqseries"},
+ 1417: {"ibm-mqseries"},
+ 1418: {"ibm-mqseries"},
+ 1419: {"ibm-mqseries"},
+ 1420: {"ibm-mqseries"},
+ 1432: {"GenericLines"},
+ 1433: {"ms-sql-s", "RPCCheck"},
+ 1440: {"JavaRMI"},
+ 1443: {"GetRequest", "SSLSessionReq"},
+ 1467: {"GenericLines"},
+ 1500: {"Verifier"},
+ 1501: {"GenericLines", "VerifierAdvanced"},
+ 1503: {"GetRequest", "TerminalServer"},
+ 1505: {"GenericLines"},
+ 1521: {"oracle-tns"},
+ 1522: {"oracle-tns"},
+ 1525: {"oracle-tns"},
+ 1526: {"oracle-tns", "informix", "drda"},
+ 1527: {"drda"},
+ 1549: {"WMSRequest"},
+ 1550: {"X11Probe"},
+ 1574: {"oracle-tns"},
+ 1583: {"pervasive-relational", "pervasive-btrieve"},
+ 1599: {"LibreOfficeImpressSCPair"},
+ 1610: {"GetRequest"},
+ 1611: {"GetRequest"},
+ 1666: {"GenericLines"},
+ 1687: {"GenericLines"},
+ 1688: {"GenericLines"},
+ 1702: {"LDAPSearchReq", "LDAPBindReq"},
+ 1720: {"TerminalServer"},
+ 1748: {"oracle-tns"},
+ 1754: {"oracle-tns"},
+ 1755: {"WMSRequest"},
+ 1761: {"LANDesk-RC"},
+ 1762: {"LANDesk-RC"},
+ 1763: {"LANDesk-RC"},
+ 1830: {"GetRequest"},
+ 1883: {"mqtt"},
+ 1900: {"GetRequest"},
+ 1911: {"niagara-fox"},
+ 1935: {"TerminalServer"},
+ 1962: {"pcworx"},
+ 1972: {"NotesRPC"},
+ 1981: {"JavaRMI"},
+ 2000: {"SSLSessionReq", "SSLv23SessionReq", "NCP"},
+ 2001: {"GetRequest"},
+ 2002: {"GetRequest", "X11Probe"},
+ 2010: {"GenericLines"},
+ 2023: {"tn3270"},
+ 2024: {"GenericLines"},
+ 2030: {"GetRequest"},
+ 2040: {"TerminalServer"},
+ 2049: {"RPCCheck"},
+ 2050: {"dominoconsole"},
+ 2064: {"GetRequest"},
+ 2068: {"DNSVersionBindReqTCP"},
+ 2100: {"FourOhFourRequest"},
+ 2105: {"DNSVersionBindReqTCP"},
+ 2160: {"GetRequest"},
+ 2181: {"Memcache"},
+ 2199: {"JavaRMI"},
+ 2221: {"SSLSessionReq"},
+ 2252: {"TLSSessionReq", "SSLSessionReq", "NJE"},
+ 2301: {"HTTPOptions"},
+ 2306: {"GetRequest"},
+ 2323: {"tn3270"},
+ 2375: {"docker"},
+ 2376: {"SSLSessionReq", "docker"},
+ 2379: {"docker"},
+ 2380: {"docker"},
+ 2396: {"GetRequest"},
+ 2401: {"Help"},
+ 2443: {"SSLSessionReq"},
+ 2481: {"giop"},
+ 2482: {"giop"},
+ 2525: {"GetRequest"},
+ 2600: {"GenericLines"},
+ 2627: {"Help"},
+ 2701: {"LANDesk-RC"},
+ 2715: {"GetRequest"},
+ 2809: {"JavaRMI"},
+ 2869: {"GetRequest"},
+ 2947: {"LPDString"},
+ 2967: {"DNSVersionBindReqTCP"},
+ 3000: {"GenericLines", "GetRequest", "Help", "NCP"},
+ 3001: {"NCP"},
+ 3002: {"GetRequest", "NCP"},
+ 3003: {"NCP"},
+ 3004: {"NCP"},
+ 3005: {"GenericLines", "NCP"},
+ 3006: {"SMBProgNeg", "NCP"},
+ 3025: {"Hello"},
+ 3031: {"NCP"},
+ 3050: {"firebird"},
+ 3052: {"GetRequest", "RTSPRequest"},
+ 3127: {"mydoom"},
+ 3128: {"GenericLines", "GetRequest", "HTTPOptions", "mydoom", "Socks5", "Socks4"},
+ 3129: {"mydoom"},
+ 3130: {"mydoom"},
+ 3131: {"mydoom"},
+ 3132: {"mydoom"},
+ 3133: {"mydoom"},
+ 3134: {"mydoom"},
+ 3135: {"mydoom"},
+ 3136: {"mydoom"},
+ 3137: {"mydoom"},
+ 3138: {"mydoom"},
+ 3139: {"mydoom"},
+ 3140: {"mydoom"},
+ 3141: {"mydoom"},
+ 3142: {"mydoom"},
+ 3143: {"mydoom"},
+ 3144: {"mydoom"},
+ 3145: {"mydoom"},
+ 3146: {"mydoom"},
+ 3147: {"mydoom"},
+ 3148: {"mydoom"},
+ 3149: {"mydoom"},
+ 3150: {"mydoom"},
+ 3151: {"mydoom"},
+ 3152: {"mydoom"},
+ 3153: {"mydoom"},
+ 3154: {"mydoom"},
+ 3155: {"mydoom"},
+ 3156: {"mydoom"},
+ 3157: {"mydoom"},
+ 3158: {"mydoom"},
+ 3159: {"mydoom"},
+ 3160: {"mydoom"},
+ 3161: {"mydoom"},
+ 3162: {"mydoom"},
+ 3163: {"mydoom"},
+ 3164: {"mydoom"},
+ 3165: {"mydoom"},
+ 3166: {"mydoom"},
+ 3167: {"mydoom"},
+ 3168: {"mydoom"},
+ 3169: {"mydoom"},
+ 3170: {"mydoom"},
+ 3171: {"mydoom"},
+ 3172: {"mydoom"},
+ 3173: {"mydoom"},
+ 3174: {"mydoom"},
+ 3175: {"mydoom"},
+ 3176: {"mydoom"},
+ 3177: {"mydoom"},
+ 3178: {"mydoom"},
+ 3179: {"mydoom"},
+ 3180: {"mydoom"},
+ 3181: {"mydoom"},
+ 3182: {"mydoom"},
+ 3183: {"mydoom"},
+ 3184: {"mydoom"},
+ 3185: {"mydoom"},
+ 3186: {"mydoom"},
+ 3187: {"mydoom"},
+ 3188: {"mydoom"},
+ 3189: {"mydoom"},
+ 3190: {"mydoom"},
+ 3191: {"mydoom"},
+ 3192: {"mydoom"},
+ 3193: {"mydoom"},
+ 3194: {"mydoom"},
+ 3195: {"mydoom"},
+ 3196: {"mydoom"},
+ 3197: {"mydoom"},
+ 3198: {"mydoom"},
+ 3268: {"LDAPSearchReq", "LDAPBindReq"},
+ 3269: {"LDAPSearchReq", "LDAPBindReq"},
+ 3273: {"JavaRMI"},
+ 3280: {"GetRequest"},
+ 3310: {"GenericLines", "VersionRequest"},
+ 3333: {"GenericLines", "LPDString", "JavaRMI", "kumo-server"},
+ 3351: {"pervasive-relational", "pervasive-btrieve"},
+ 3372: {"GetRequest", "RTSPRequest"},
+ 3388: {"TLSSessionReq", "TerminalServerCookie", "TerminalServer"},
+ 3389: {"TerminalServerCookie", "TerminalServer", "TLSSessionReq"},
+ 3443: {"GetRequest", "SSLSessionReq"},
+ 3493: {"Help"},
+ 3531: {"GetRequest"},
+ 3632: {"DistCCD"},
+ 3689: {"GetRequest"},
+ 3790: {"metasploit-msgrpc"},
+ 3872: {"GetRequest"},
+ 3892: {"LDAPSearchReq", "LDAPBindReq"},
+ 3900: {"SMBProgNeg", "JavaRMI"},
+ 3940: {"GenericLines"},
+ 4000: {"GetRequest", "NoMachine"},
+ 4035: {"LDAPBindReq", "LDAPBindReq"},
+ 4045: {"RPCCheck"},
+ 4155: {"GenericLines"},
+ 4369: {"epmd"},
+ 4433: {"TLSSessionReq", "SSLSessionReq", "SSLv23SessionReq"},
+ 4443: {"GetRequest", "HTTPOptions", "SSLSessionReq", "FourOhFourRequest"},
+ 4444: {"GetRequest", "TLSSessionReq", "SSLSessionReq", "SSLv23SessionReq"},
+ 4533: {"rotctl"},
+ 4567: {"GetRequest"},
+ 4660: {"GetRequest"},
+ 4711: {"GetRequest", "piholeVersion"},
+ 4899: {"Radmin"},
+ 4911: {"SSLSessionReq", "niagara-fox"},
+ 4999: {"RPCCheck"},
+ 5000: {"GenericLines", "GetRequest", "RTSPRequest", "DNSVersionBindReqTCP", "SMBProgNeg", "ZendJavaBridge"},
+ 5001: {"WMSRequest", "ZendJavaBridge"},
+ 5002: {"ZendJavaBridge"},
+ 5009: {"SMBProgNeg"},
+ 5060: {"GetRequest", "SIPOptions"},
+ 5061: {"GetRequest", "TLSSessionReq", "SSLSessionReq", "SIPOptions"},
+ 5201: {"iperf3"},
+ 5222: {"GetRequest"},
+ 5232: {"HTTPOptions"},
+ 5269: {"GetRequest"},
+ 5280: {"GetRequest"},
+ 5302: {"X11Probe"},
+ 5323: {"DNSVersionBindReqTCP"},
+ 5400: {"GenericLines"},
+ 5427: {"GetRequest"},
+ 5432: {"GenericLines", "GetRequest", "SMBProgNeg"},
+ 5443: {"SSLSessionReq"},
+ 5520: {"DNSVersionBindReqTCP", "JavaRMI"},
+ 5521: {"JavaRMI"},
+ 5530: {"DNSVersionBindReqTCP"},
+ 5550: {"SSLSessionReq", "SSLv23SessionReq"},
+ 5555: {"GenericLines", "DNSVersionBindReqTCP", "SMBProgNeg", "adbConnect"},
+ 5556: {"DNSVersionBindReqTCP"},
+ 5570: {"GenericLines"},
+ 5580: {"JavaRMI"},
+ 5600: {"SMBProgNeg"},
+ 5701: {"hazelcast-http"},
+ 5702: {"hazelcast-http"},
+ 5703: {"hazelcast-http"},
+ 5704: {"hazelcast-http"},
+ 5705: {"hazelcast-http"},
+ 5706: {"hazelcast-http"},
+ 5707: {"hazelcast-http"},
+ 5708: {"hazelcast-http"},
+ 5709: {"LANDesk-RC", "hazelcast-http"},
+ 5800: {"GetRequest"},
+ 5801: {"GetRequest"},
+ 5802: {"GetRequest"},
+ 5803: {"GetRequest"},
+ 5868: {"SSLSessionReq"},
+ 5900: {"GetRequest"},
+ 5985: {"GetRequest"},
+ 5986: {"GetRequest", "SSLSessionReq"},
+ 5999: {"JavaRMI"},
+ 6000: {"HTTPOptions", "X11Probe"},
+ 6001: {"X11Probe"},
+ 6002: {"X11Probe"},
+ 6003: {"X11Probe"},
+ 6004: {"X11Probe"},
+ 6005: {"X11Probe"},
+ 6006: {"X11Probe"},
+ 6007: {"X11Probe"},
+ 6008: {"X11Probe"},
+ 6009: {"X11Probe"},
+ 6010: {"X11Probe"},
+ 6011: {"X11Probe"},
+ 6012: {"X11Probe"},
+ 6013: {"X11Probe"},
+ 6014: {"X11Probe"},
+ 6015: {"X11Probe"},
+ 6016: {"X11Probe"},
+ 6017: {"X11Probe"},
+ 6018: {"X11Probe"},
+ 6019: {"X11Probe"},
+ 6020: {"X11Probe"},
+ 6050: {"DNSStatusRequestTCP"},
+ 6060: {"JavaRMI"},
+ 6103: {"GetRequest"},
+ 6112: {"GenericLines"},
+ 6163: {"HELP4STOMP"},
+ 6251: {"SSLSessionReq"},
+ 6346: {"GetRequest"},
+ 6379: {"redis-server"},
+ 6432: {"GenericLines"},
+ 6443: {"SSLSessionReq"},
+ 6543: {"DNSVersionBindReqTCP"},
+ 6544: {"GetRequest"},
+ 6560: {"Help"},
+ 6588: {"Socks5", "Socks4"},
+ 6600: {"GetRequest"},
+ 6660: {"Socks5", "Socks4"},
+ 6661: {"Socks5", "Socks4"},
+ 6662: {"Socks5", "Socks4"},
+ 6663: {"Socks5", "Socks4"},
+ 6664: {"Socks5", "Socks4"},
+ 6665: {"Socks5", "Socks4"},
+ 6666: {"Help", "Socks5", "Socks4", "beast2", "vp3"},
+ 6667: {"GenericLines", "Help", "Socks5", "Socks4"},
+ 6668: {"GenericLines", "Help", "Socks5", "Socks4"},
+ 6669: {"GenericLines", "Help", "Socks5", "Socks4"},
+ 6670: {"GenericLines", "Help"},
+ 6679: {"TLSSessionReq", "SSLSessionReq"},
+ 6697: {"TLSSessionReq", "SSLSessionReq"},
+ 6699: {"GetRequest"},
+ 6715: {"JMON", "JMON"},
+ 6789: {"JavaRMI"},
+ 6802: {"NCP"},
+ 6969: {"GetRequest"},
+ 6996: {"JavaRMI"},
+ 7000: {"RPCCheck", "DNSVersionBindReqTCP", "SSLSessionReq", "X11Probe"},
+ 7002: {"GetRequest"},
+ 7007: {"GetRequest"},
+ 7008: {"DNSVersionBindReqTCP"},
+ 7070: {"GetRequest", "RTSPRequest"},
+ 7100: {"GetRequest", "X11Probe"},
+ 7101: {"X11Probe"},
+ 7144: {"GenericLines"},
+ 7145: {"GenericLines"},
+ 7171: {"NotesRPC"},
+ 7200: {"GenericLines"},
+ 7210: {"SSLSessionReq", "SSLv23SessionReq"},
+ 7272: {"SSLSessionReq", "SSLv23SessionReq"},
+ 7402: {"GetRequest"},
+ 7443: {"GetRequest", "SSLSessionReq"},
+ 7461: {"SMBProgNeg"},
+ 7700: {"JavaRMI"},
+ 7776: {"GetRequest"},
+ 7777: {"X11Probe", "Socks5", "Arucer"},
+ 7780: {"GenericLines"},
+ 7800: {"JavaRMI"},
+ 7801: {"JavaRMI"},
+ 7878: {"JavaRMI"},
+ 7887: {"xmlsysd"},
+ 7890: {"JavaRMI"},
+ 8000: {"GenericLines", "GetRequest", "X11Probe", "FourOhFourRequest", "Socks5", "Socks4"},
+ 8001: {"GetRequest", "FourOhFourRequest"},
+ 8002: {"GetRequest", "FourOhFourRequest"},
+ 8003: {"GetRequest", "FourOhFourRequest"},
+ 8004: {"GetRequest", "FourOhFourRequest"},
+ 8005: {"GetRequest", "FourOhFourRequest"},
+ 8006: {"GetRequest", "FourOhFourRequest"},
+ 8007: {"GetRequest", "FourOhFourRequest"},
+ 8008: {"GetRequest", "FourOhFourRequest", "Socks5", "Socks4", "ajp"},
+ 8009: {"GetRequest", "SSLSessionReq", "SSLv23SessionReq", "FourOhFourRequest", "ajp"},
+ 8010: {"GetRequest", "FourOhFourRequest", "Socks5"},
+ 8050: {"JavaRMI"},
+ 8051: {"JavaRMI"},
+ 8080: {"GetRequest", "HTTPOptions", "RTSPRequest", "FourOhFourRequest", "Socks5", "Socks4"},
+ 8081: {"GetRequest", "FourOhFourRequest", "SIPOptions", "WWWOFFLEctrlstat"},
+ 8082: {"GetRequest", "FourOhFourRequest"},
+ 8083: {"GetRequest", "FourOhFourRequest"},
+ 8084: {"GetRequest", "FourOhFourRequest"},
+ 8085: {"GetRequest", "FourOhFourRequest", "JavaRMI"},
+ 8087: {"riak-pbc"},
+ 8088: {"GetRequest", "Socks5", "Socks4"},
+ 8091: {"JavaRMI"},
+ 8118: {"GetRequest"},
+ 8138: {"GenericLines"},
+ 8181: {"GetRequest", "SSLSessionReq"},
+ 8194: {"SSLSessionReq", "SSLv23SessionReq"},
+ 8205: {"JavaRMI"},
+ 8303: {"JavaRMI"},
+ 8307: {"RPCCheck"},
+ 8333: {"RPCCheck"},
+ 8443: {"GetRequest", "HTTPOptions", "TLSSessionReq", "SSLSessionReq", "SSLv23SessionReq", "FourOhFourRequest"},
+ 8530: {"GetRequest"},
+ 8531: {"GetRequest", "SSLSessionReq"},
+ 8642: {"JavaRMI"},
+ 8686: {"JavaRMI"},
+ 8701: {"JavaRMI"},
+ 8728: {"NotesRPC"},
+ 8770: {"apple-iphoto"},
+ 8880: {"GetRequest", "FourOhFourRequest"},
+ 8881: {"GetRequest", "FourOhFourRequest"},
+ 8882: {"GetRequest", "FourOhFourRequest"},
+ 8883: {"GetRequest", "TLSSessionReq", "SSLSessionReq", "FourOhFourRequest", "mqtt"},
+ 8884: {"GetRequest", "FourOhFourRequest"},
+ 8885: {"GetRequest", "FourOhFourRequest"},
+ 8886: {"GetRequest", "FourOhFourRequest"},
+ 8887: {"GetRequest", "FourOhFourRequest"},
+ 8888: {"GetRequest", "HTTPOptions", "FourOhFourRequest", "JavaRMI", "LSCP"},
+ 8889: {"JavaRMI"},
+ 8890: {"JavaRMI"},
+ 8901: {"JavaRMI"},
+ 8902: {"JavaRMI"},
+ 8903: {"JavaRMI"},
+ 8999: {"JavaRMI"},
+ 9000: {"GenericLines", "GetRequest"},
+ 9001: {"GenericLines", "GetRequest", "TLSSessionReq", "SSLSessionReq", "SSLv23SessionReq", "JavaRMI", "Radmin", "mongodb", "tarantool", "tor-versions"},
+ 9002: {"GenericLines", "tor-versions"},
+ 9003: {"GenericLines", "JavaRMI"},
+ 9004: {"JavaRMI"},
+ 9005: {"JavaRMI"},
+ 9030: {"GetRequest"},
+ 9050: {"GetRequest", "JavaRMI"},
+ 9080: {"GetRequest"},
+ 9088: {"informix", "drda"},
+ 9089: {"informix", "drda"},
+ 9090: {"GetRequest", "JavaRMI", "WMSRequest", "ibm-db2-das", "SqueezeCenter_CLI", "informix", "drda"},
+ 9091: {"informix", "drda"},
+ 9092: {"informix", "drda"},
+ 9093: {"informix", "drda"},
+ 9094: {"informix", "drda"},
+ 9095: {"informix", "drda"},
+ 9096: {"informix", "drda"},
+ 9097: {"informix", "drda"},
+ 9098: {"informix", "drda"},
+ 9099: {"JavaRMI", "informix", "drda"},
+ 9100: {"hp-pjl", "informix", "drda"},
+ 9101: {"hp-pjl"},
+ 9102: {"SMBProgNeg", "hp-pjl"},
+ 9103: {"SMBProgNeg", "hp-pjl"},
+ 9104: {"hp-pjl"},
+ 9105: {"hp-pjl"},
+ 9106: {"hp-pjl"},
+ 9107: {"hp-pjl"},
+ 9300: {"JavaRMI"},
+ 9390: {"metasploit-xmlrpc"},
+ 9443: {"GetRequest", "SSLSessionReq"},
+ 9481: {"Socks5"},
+ 9500: {"JavaRMI"},
+ 9711: {"JavaRMI"},
+ 9761: {"insteonPLM"},
+ 9801: {"GenericLines"},
+ 9809: {"JavaRMI"},
+ 9810: {"JavaRMI"},
+ 9811: {"JavaRMI"},
+ 9812: {"JavaRMI"},
+ 9813: {"JavaRMI"},
+ 9814: {"JavaRMI"},
+ 9815: {"JavaRMI"},
+ 9875: {"JavaRMI"},
+ 9910: {"JavaRMI"},
+ 9930: {"ibm-db2-das"},
+ 9931: {"ibm-db2-das"},
+ 9932: {"ibm-db2-das"},
+ 9933: {"ibm-db2-das"},
+ 9934: {"ibm-db2-das"},
+ 9991: {"JavaRMI"},
+ 9998: {"teamspeak-tcpquery-ver"},
+ 9999: {"GetRequest", "HTTPOptions", "FourOhFourRequest", "JavaRMI"},
+ 10000: {"GetRequest", "HTTPOptions", "RTSPRequest"},
+ 10001: {"GetRequest", "JavaRMI", "ZendJavaBridge"},
+ 10002: {"ZendJavaBridge", "SharpTV"},
+ 10003: {"ZendJavaBridge"},
+ 10005: {"GetRequest"},
+ 10031: {"HTTPOptions"},
+ 10098: {"JavaRMI"},
+ 10099: {"JavaRMI"},
+ 10162: {"JavaRMI"},
+ 10333: {"teamtalk-login"},
+ 10443: {"GetRequest", "SSLSessionReq"},
+ 10990: {"JavaRMI"},
+ 11001: {"JavaRMI"},
+ 11099: {"JavaRMI"},
+ 11210: {"couchbase-data"},
+ 11211: {"Memcache"},
+ 11333: {"JavaRMI"},
+ 11371: {"GenericLines", "GetRequest"},
+ 11711: {"LDAPSearchReq"},
+ 11712: {"LDAPSearchReq"},
+ 11965: {"GenericLines"},
+ 12000: {"JavaRMI"},
+ 12345: {"Help", "OfficeScan"},
+ 13013: {"GetRequest", "JavaRMI"},
+ 13666: {"GetRequest"},
+ 13720: {"GenericLines"},
+ 13722: {"GetRequest"},
+ 13783: {"DNSVersionBindReqTCP"},
+ 14000: {"JavaRMI"},
+ 14238: {"oracle-tns"},
+ 14443: {"GetRequest", "SSLSessionReq"},
+ 14534: {"GetRequest"},
+ 14690: {"Help"},
+ 15000: {"GenericLines", "GetRequest", "JavaRMI"},
+ 15001: {"GenericLines", "JavaRMI"},
+ 15002: {"GenericLines", "SSLSessionReq"},
+ 15200: {"JavaRMI"},
+ 16000: {"JavaRMI"},
+ 17007: {"RPCCheck"},
+ 17200: {"JavaRMI"},
+ 17988: {"GetRequest"},
+ 18086: {"GenericLines"},
+ 18182: {"SMBProgNeg"},
+ 18264: {"GetRequest"},
+ 18980: {"JavaRMI"},
+ 19150: {"GenericLines", "gkrellm"},
+ 19350: {"LPDString"},
+ 19700: {"kumo-server"},
+ 19800: {"kumo-server"},
+ 20000: {"JavaRMI", "oracle-tns"},
+ 20547: {"proconos"},
+ 22001: {"NotesRPC"},
+ 22490: {"Help"},
+ 23791: {"JavaRMI"},
+ 25565: {"minecraft-ping"},
+ 26214: {"GenericLines"},
+ 26256: {"JavaRMI"},
+ 26470: {"GenericLines"},
+ 27000: {"SMBProgNeg"},
+ 27001: {"SMBProgNeg"},
+ 27002: {"SMBProgNeg"},
+ 27003: {"SMBProgNeg"},
+ 27004: {"SMBProgNeg"},
+ 27005: {"SMBProgNeg"},
+ 27006: {"SMBProgNeg"},
+ 27007: {"SMBProgNeg"},
+ 27008: {"SMBProgNeg"},
+ 27009: {"SMBProgNeg"},
+ 27010: {"SMBProgNeg"},
+ 27017: {"mongodb"},
+ 27036: {"TLS-PSK"},
+ 30444: {"GenericLines"},
+ 31099: {"JavaRMI"},
+ 31337: {"GetRequest", "SIPOptions"},
+ 31416: {"GenericLines"},
+ 32211: {"LPDString"},
+ 32750: {"RPCCheck"},
+ 32751: {"RPCCheck"},
+ 32752: {"RPCCheck"},
+ 32753: {"RPCCheck"},
+ 32754: {"RPCCheck"},
+ 32755: {"RPCCheck"},
+ 32756: {"RPCCheck"},
+ 32757: {"RPCCheck"},
+ 32758: {"RPCCheck"},
+ 32759: {"RPCCheck"},
+ 32760: {"RPCCheck"},
+ 32761: {"RPCCheck"},
+ 32762: {"RPCCheck"},
+ 32763: {"RPCCheck"},
+ 32764: {"RPCCheck"},
+ 32765: {"RPCCheck"},
+ 32766: {"RPCCheck"},
+ 32767: {"RPCCheck"},
+ 32768: {"RPCCheck"},
+ 32769: {"RPCCheck"},
+ 32770: {"RPCCheck"},
+ 32771: {"RPCCheck"},
+ 32772: {"RPCCheck"},
+ 32773: {"RPCCheck"},
+ 32774: {"RPCCheck"},
+ 32775: {"RPCCheck"},
+ 32776: {"RPCCheck"},
+ 32777: {"RPCCheck"},
+ 32778: {"RPCCheck"},
+ 32779: {"RPCCheck"},
+ 32780: {"RPCCheck"},
+ 32781: {"RPCCheck"},
+ 32782: {"RPCCheck"},
+ 32783: {"RPCCheck"},
+ 32784: {"RPCCheck"},
+ 32785: {"RPCCheck"},
+ 32786: {"RPCCheck"},
+ 32787: {"RPCCheck"},
+ 32788: {"RPCCheck"},
+ 32789: {"RPCCheck"},
+ 32790: {"RPCCheck"},
+ 32791: {"RPCCheck"},
+ 32792: {"RPCCheck"},
+ 32793: {"RPCCheck"},
+ 32794: {"RPCCheck"},
+ 32795: {"RPCCheck"},
+ 32796: {"RPCCheck"},
+ 32797: {"RPCCheck"},
+ 32798: {"RPCCheck"},
+ 32799: {"RPCCheck"},
+ 32800: {"RPCCheck"},
+ 32801: {"RPCCheck"},
+ 32802: {"RPCCheck"},
+ 32803: {"RPCCheck"},
+ 32804: {"RPCCheck"},
+ 32805: {"RPCCheck"},
+ 32806: {"RPCCheck"},
+ 32807: {"RPCCheck"},
+ 32808: {"RPCCheck"},
+ 32809: {"RPCCheck"},
+ 32810: {"RPCCheck"},
+ 32913: {"JavaRMI"},
+ 33000: {"JavaRMI"},
+ 33015: {"tarantool"},
+ 34012: {"GenericLines"},
+ 37435: {"HTTPOptions"},
+ 37718: {"JavaRMI"},
+ 38978: {"RPCCheck"},
+ 40193: {"GetRequest"},
+ 41523: {"DNSStatusRequestTCP"},
+ 44443: {"GetRequest", "SSLSessionReq"},
+ 45230: {"JavaRMI"},
+ 47001: {"JavaRMI"},
+ 47002: {"JavaRMI"},
+ 49152: {"FourOhFourRequest"},
+ 49153: {"mongodb"},
+ 49400: {"HTTPOptions"},
+ 50000: {"GetRequest", "ibm-db2-das", "ibm-db2", "drda"},
+ 50001: {"ibm-db2"},
+ 50002: {"ibm-db2"},
+ 50003: {"ibm-db2"},
+ 50004: {"ibm-db2"},
+ 50005: {"ibm-db2"},
+ 50006: {"ibm-db2"},
+ 50007: {"ibm-db2"},
+ 50008: {"ibm-db2"},
+ 50009: {"ibm-db2"},
+ 50010: {"ibm-db2"},
+ 50011: {"ibm-db2"},
+ 50012: {"ibm-db2"},
+ 50013: {"ibm-db2"},
+ 50014: {"ibm-db2"},
+ 50015: {"ibm-db2"},
+ 50016: {"ibm-db2"},
+ 50017: {"ibm-db2"},
+ 50018: {"ibm-db2"},
+ 50019: {"ibm-db2"},
+ 50020: {"ibm-db2"},
+ 50021: {"ibm-db2"},
+ 50022: {"ibm-db2"},
+ 50023: {"ibm-db2"},
+ 50024: {"ibm-db2"},
+ 50025: {"ibm-db2"},
+ 50050: {"JavaRMI"},
+ 50500: {"JavaRMI"},
+ 50501: {"JavaRMI"},
+ 50502: {"JavaRMI"},
+ 50503: {"JavaRMI"},
+ 50504: {"JavaRMI"},
+ 50505: {"metasploit-msgrpc"},
+ 51234: {"teamspeak-tcpquery-ver"},
+ 55552: {"metasploit-msgrpc"},
+ 55553: {"metasploit-xmlrpc", "metasploit-xmlrpc"},
+ 55555: {"GetRequest"},
+ 56667: {"GenericLines"},
+ 59100: {"kumo-server"},
+ 60000: {"ibm-db2", "drda"},
+ 60001: {"ibm-db2"},
+ 60002: {"ibm-db2"},
+ 60003: {"ibm-db2"},
+ 60004: {"ibm-db2"},
+ 60005: {"ibm-db2"},
+ 60006: {"ibm-db2"},
+ 60007: {"ibm-db2"},
+ 60008: {"ibm-db2"},
+ 60009: {"ibm-db2"},
+ 60010: {"ibm-db2"},
+ 60011: {"ibm-db2"},
+ 60012: {"ibm-db2"},
+ 60013: {"ibm-db2"},
+ 60014: {"ibm-db2"},
+ 60015: {"ibm-db2"},
+ 60016: {"ibm-db2"},
+ 60017: {"ibm-db2"},
+ 60018: {"ibm-db2"},
+ 60019: {"ibm-db2"},
+ 60020: {"ibm-db2"},
+ 60021: {"ibm-db2"},
+ 60022: {"ibm-db2"},
+ 60023: {"ibm-db2"},
+ 60024: {"ibm-db2"},
+ 60025: {"ibm-db2"},
+ 60443: {"GetRequest", "SSLSessionReq"},
+ 61613: {"HELP4STOMP"},
+}
+
+var Passwords = []string{"123456", "admin", "admin123", "root", "", "pass123", "pass@123", "password", "Password", "P@ssword123", "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", "elastic123"}
+
+var (
+ Outputfile string // 输出文件路径
+ OutputFormat string // 输出格式
+)
+
+// 添加一个全局的进度条变量
+var ProgressBar *progressbar.ProgressBar
+
+// 添加一个全局互斥锁来控制输出
+var OutputMutex sync.Mutex
+
+type PocInfo struct {
+ Target string
+ PocName string
+}
+
+var (
+ // 目标配置
+ Ports string
+ ExcludePorts string // 原NoPorts
+ ExcludeHosts string
+ AddPorts string // 原PortAdd
+
+ // 认证配置
+ Username string
+ Password string
+ Domain string
+ SshKeyPath string // 原SshKey
+ AddUsers string // 原UserAdd
+ AddPasswords string // 原PassAdd
+
+ // 扫描配置
+ ScanMode string // 原Scantype
+ ThreadNum int // 原Threads
+ //UseSynScan bool
+ Timeout int64 = 3
+ LiveTop int
+ DisablePing bool // 原NoPing
+ UsePing bool // 原Ping
+ Command string
+ SkipFingerprint bool
+
+ // 文件配置
+ HostsFile string // 原HostFile
+ UsersFile string // 原Userfile
+ PasswordsFile string // 原Passfile
+ HashFile string // 原Hashfile
+ PortsFile string // 原PortFile
+
+ // Web配置
+ TargetURL string // 原URL
+ URLsFile string // 原UrlFile
+ URLs []string // 原Urls
+ WebTimeout int64 = 5
+ HttpProxy string // 原Proxy
+ Socks5Proxy string
+
+ LocalMode bool // -local 本地模式
+
+ // POC配置
+ PocPath string
+ Pocinfo PocInfo
+
+ // Redis配置
+ RedisFile string
+ RedisShell string
+ DisableRedis bool // 原Noredistest
+
+ // 爆破配置
+ DisableBrute bool // 原IsBrute
+ BruteThreads int // 原BruteThread
+ MaxRetries int // 最大重试次数
+
+ // 其他配置
+ RemotePath string // 原Path
+ HashValue string // 原Hash
+ HashValues []string // 原Hashs
+ HashBytes [][]byte
+ HostPort []string
+ Shellcode string // 原SC
+ EnableWmi bool // 原IsWmi
+
+ // 输出配置
+ DisableSave bool // 禁止保存结果
+ Silent bool // 静默模式
+ NoColor bool // 禁用彩色输出
+ JsonFormat bool // JSON格式输出
+ LogLevel string // 日志输出级别
+ ShowProgress bool // 是否显示进度条
+
+ Language string // 语言
+)
+
+var (
+ UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36"
+ Accept = "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9"
+ DnsLog bool
+ PocNum int
+ PocFull bool
+ CeyeDomain string
+ ApiKey string
+ Cookie string
+)
diff --git a/Common/Flag.go b/Common/Flag.go
new file mode 100644
index 0000000..54df9f5
--- /dev/null
+++ b/Common/Flag.go
@@ -0,0 +1,138 @@
+package Common
+
+import (
+ "flag"
+ "fmt"
+ "github.com/fatih/color"
+ "strings"
+)
+
+func Banner() {
+ // 定义暗绿色系
+ colors := []color.Attribute{
+ color.FgGreen, // 基础绿
+ color.FgHiGreen, // 亮绿
+ }
+
+ lines := []string{
+ " ___ _ ",
+ " / _ \\ ___ ___ _ __ __ _ ___| | __ ",
+ " / /_\\/____/ __|/ __| '__/ _` |/ __| |/ /",
+ "/ /_\\\\_____\\__ \\ (__| | | (_| | (__| < ",
+ "\\____/ |___/\\___|_| \\__,_|\\___|_|\\_\\ ",
+ }
+
+ // 获取最长行的长度
+ maxLength := 0
+ for _, line := range lines {
+ if len(line) > maxLength {
+ maxLength = len(line)
+ }
+ }
+
+ // 创建边框
+ topBorder := "┌" + strings.Repeat("─", maxLength+2) + "┐"
+ bottomBorder := "└" + strings.Repeat("─", maxLength+2) + "┘"
+
+ // 打印banner
+ fmt.Println(topBorder)
+
+ for lineNum, line := range lines {
+ fmt.Print("│ ")
+ // 使用对应的颜色打印每个字符
+ c := color.New(colors[lineNum%2])
+ c.Print(line)
+ // 补齐空格
+ padding := maxLength - len(line)
+ fmt.Printf("%s │\n", strings.Repeat(" ", padding))
+ }
+
+ fmt.Println(bottomBorder)
+
+ // 打印版本信息
+ c := color.New(colors[1])
+ c.Printf(" Fscan Version: %s\n\n", version)
+}
+
+func Flag(Info *HostInfo) {
+ Banner()
+
+ // 目标配置
+ flag.StringVar(&Info.Host, "h", "", GetText("flag_host"))
+ flag.StringVar(&ExcludeHosts, "eh", "", GetText("flag_exclude_hosts"))
+ flag.StringVar(&Ports, "p", MainPorts, GetText("flag_ports"))
+
+ // 认证配置
+ flag.StringVar(&AddUsers, "usera", "", GetText("flag_add_users"))
+ flag.StringVar(&AddPasswords, "pwda", "", GetText("flag_add_passwords"))
+ flag.StringVar(&Username, "user", "", GetText("flag_username"))
+ flag.StringVar(&Password, "pwd", "", GetText("flag_password"))
+ flag.StringVar(&Domain, "domain", "", GetText("flag_domain"))
+ flag.StringVar(&SshKeyPath, "sshkey", "", GetText("flag_ssh_key"))
+
+ // 扫描配置
+ flag.StringVar(&ScanMode, "m", "All", GetText("flag_scan_mode"))
+ flag.IntVar(&ThreadNum, "t", 60, GetText("flag_thread_num"))
+ flag.Int64Var(&Timeout, "time", 3, GetText("flag_timeout"))
+ flag.IntVar(&LiveTop, "top", 10, GetText("flag_live_top"))
+ flag.BoolVar(&DisablePing, "np", false, GetText("flag_disable_ping"))
+ flag.BoolVar(&UsePing, "ping", false, GetText("flag_use_ping"))
+ flag.StringVar(&Command, "c", "", GetText("flag_command"))
+ flag.BoolVar(&SkipFingerprint, "skip", false, GetText("flag_skip_fingerprint"))
+
+ // 文件配置
+ flag.StringVar(&HostsFile, "hf", "", GetText("flag_hosts_file"))
+ flag.StringVar(&UsersFile, "userf", "", GetText("flag_users_file"))
+ flag.StringVar(&PasswordsFile, "pwdf", "", GetText("flag_passwords_file"))
+ flag.StringVar(&HashFile, "hashf", "", GetText("flag_hash_file"))
+ flag.StringVar(&PortsFile, "portf", "", GetText("flag_ports_file"))
+
+ // Web配置
+ flag.StringVar(&TargetURL, "u", "", GetText("flag_target_url"))
+ flag.StringVar(&URLsFile, "uf", "", GetText("flag_urls_file"))
+ flag.StringVar(&Cookie, "cookie", "", GetText("flag_cookie"))
+ flag.Int64Var(&WebTimeout, "wt", 5, GetText("flag_web_timeout"))
+ flag.StringVar(&HttpProxy, "proxy", "", GetText("flag_http_proxy"))
+ flag.StringVar(&Socks5Proxy, "socks5", "", GetText("flag_socks5_proxy"))
+
+ // 本地扫描配置
+ flag.BoolVar(&LocalMode, "local", false, GetText("flag_local_mode"))
+
+ // POC配置
+ flag.StringVar(&PocPath, "pocpath", "", GetText("flag_poc_path"))
+ flag.StringVar(&Pocinfo.PocName, "pocname", "", GetText("flag_poc_name"))
+ flag.BoolVar(&PocFull, "full", false, GetText("flag_poc_full"))
+ flag.BoolVar(&DnsLog, "dns", false, GetText("flag_dns_log"))
+ flag.IntVar(&PocNum, "num", 20, GetText("flag_poc_num"))
+
+ // Redis利用配置
+ flag.StringVar(&RedisFile, "rf", "", GetText("flag_redis_file"))
+ flag.StringVar(&RedisShell, "rs", "", GetText("flag_redis_shell"))
+ flag.BoolVar(&DisableRedis, "noredis", false, GetText("flag_disable_redis"))
+
+ // 暴力破解配置
+ flag.BoolVar(&DisableBrute, "nobr", false, GetText("flag_disable_brute"))
+ flag.IntVar(&MaxRetries, "retry", 3, GetText("flag_max_retries"))
+
+ // 其他配置
+ flag.StringVar(&RemotePath, "path", "", GetText("flag_remote_path"))
+ flag.StringVar(&HashValue, "hash", "", GetText("flag_hash_value"))
+ flag.StringVar(&Shellcode, "sc", "", GetText("flag_shellcode"))
+ flag.BoolVar(&EnableWmi, "wmi", false, GetText("flag_enable_wmi"))
+
+ // 输出配置
+ flag.StringVar(&Outputfile, "o", "result.txt", GetText("flag_output_file"))
+ flag.StringVar(&OutputFormat, "f", "txt", GetText("flag_output_format"))
+ flag.BoolVar(&DisableSave, "no", false, GetText("flag_disable_save"))
+ flag.BoolVar(&Silent, "silent", false, GetText("flag_silent_mode"))
+ flag.BoolVar(&NoColor, "nocolor", false, GetText("flag_no_color"))
+ flag.BoolVar(&JsonFormat, "json", false, GetText("flag_json_format"))
+ flag.StringVar(&LogLevel, "log", LogLevelSuccess, GetText("flag_log_level"))
+ flag.BoolVar(&ShowProgress, "pg", false, GetText("flag_show_progress"))
+
+ flag.StringVar(&Language, "lang", "zh", GetText("flag_language"))
+
+ flag.Parse()
+
+ SetLanguage()
+}
diff --git a/Common/Log.go b/Common/Log.go
new file mode 100644
index 0000000..a645223
--- /dev/null
+++ b/Common/Log.go
@@ -0,0 +1,238 @@
+package Common
+
+import (
+ "fmt"
+ "io"
+ "log"
+ "path/filepath"
+ "runtime"
+ "strings"
+ "sync"
+ "time"
+
+ "github.com/fatih/color"
+)
+
+// 全局变量定义
+var (
+ // 扫描状态管理器,记录最近一次成功和错误的时间
+ status = &ScanStatus{lastSuccess: time.Now(), lastError: time.Now()}
+
+ // Num 表示待处理的总任务数量
+ Num int64
+ // End 表示已经完成的任务数量
+ End int64
+)
+
+// ScanStatus 用于记录和管理扫描状态的结构体
+type ScanStatus struct {
+ mu sync.RWMutex // 读写互斥锁,用于保护并发访问
+ total int64 // 总任务数
+ completed int64 // 已完成任务数
+ lastSuccess time.Time // 最近一次成功的时间
+ lastError time.Time // 最近一次错误的时间
+}
+
+// LogEntry 定义单条日志的结构
+type LogEntry struct {
+ Level string // 日志级别: ERROR/INFO/SUCCESS/DEBUG
+ Time time.Time // 日志时间
+ Content string // 日志内容
+}
+
+// 定义系统支持的日志级别常量
+const (
+ LogLevelAll = "ALL" // 显示所有级别日志
+ LogLevelError = "ERROR" // 仅显示错误日志
+ LogLevelInfo = "INFO" // 仅显示信息日志
+ LogLevelSuccess = "SUCCESS" // 仅显示成功日志
+ LogLevelDebug = "DEBUG" // 仅显示调试日志
+)
+
+// 日志级别对应的显示颜色映射
+var logColors = map[string]color.Attribute{
+ LogLevelError: color.FgRed, // 错误日志显示红色
+ LogLevelInfo: color.FgYellow, // 信息日志显示黄色
+ LogLevelSuccess: color.FgGreen, // 成功日志显示绿色
+ LogLevelDebug: color.FgBlue, // 调试日志显示蓝色
+}
+
+// InitLogger 初始化日志系统
+func InitLogger() {
+ // 禁用标准日志输出
+ log.SetOutput(io.Discard)
+}
+
+// formatLogMessage 格式化日志消息为标准格式
+// 返回格式:[时间] [级别] 内容
+func formatLogMessage(entry *LogEntry) string {
+ timeStr := entry.Time.Format("2006-01-02 15:04:05")
+ return fmt.Sprintf("[%s] [%s] %s", timeStr, entry.Level, entry.Content)
+}
+
+// printLog 根据日志级别打印日志
+func printLog(entry *LogEntry) {
+ // 根据当前设置的日志级别过滤日志
+ shouldPrint := false
+ switch LogLevel {
+ case LogLevelDebug:
+ // DEBUG级别显示所有日志
+ shouldPrint = true
+ case LogLevelError:
+ // ERROR级别显示 ERROR、SUCCESS、INFO
+ shouldPrint = entry.Level == LogLevelError ||
+ entry.Level == LogLevelSuccess ||
+ entry.Level == LogLevelInfo
+ case LogLevelSuccess:
+ // SUCCESS级别显示 SUCCESS、INFO
+ shouldPrint = entry.Level == LogLevelSuccess ||
+ entry.Level == LogLevelInfo
+ case LogLevelInfo:
+ // INFO级别只显示 INFO
+ shouldPrint = entry.Level == LogLevelInfo
+ case LogLevelAll:
+ // ALL显示所有日志
+ shouldPrint = true
+ default:
+ // 默认只显示 INFO
+ shouldPrint = entry.Level == LogLevelInfo
+ }
+
+ if !shouldPrint {
+ return
+ }
+
+ OutputMutex.Lock()
+ defer OutputMutex.Unlock()
+
+ // 处理进度条
+ clearAndWaitProgress()
+
+ // 打印日志消息
+ logMsg := formatLogMessage(entry)
+ if !NoColor {
+ // 使用彩色输出
+ if colorAttr, ok := logColors[entry.Level]; ok {
+ color.New(colorAttr).Println(logMsg)
+ } else {
+ fmt.Println(logMsg)
+ }
+ } else {
+ // 普通输出
+ fmt.Println(logMsg)
+ }
+
+ // 等待日志输出完成
+ time.Sleep(50 * time.Millisecond)
+
+ // 重新显示进度条
+ if ProgressBar != nil {
+ ProgressBar.RenderBlank()
+ }
+}
+
+// clearAndWaitProgress 清除进度条并等待
+func clearAndWaitProgress() {
+ if ProgressBar != nil {
+ ProgressBar.Clear()
+ time.Sleep(10 * time.Millisecond)
+ }
+}
+
+// LogError 记录错误日志,自动包含文件名和行号信息
+func LogError(errMsg string) {
+ // 获取调用者的文件名和行号
+ _, file, line, ok := runtime.Caller(1)
+ if !ok {
+ file = "unknown"
+ line = 0
+ }
+ file = filepath.Base(file)
+
+ errorMsg := fmt.Sprintf("%s:%d - %s", file, line, errMsg)
+
+ entry := &LogEntry{
+ Level: LogLevelError,
+ Time: time.Now(),
+ Content: errorMsg,
+ }
+
+ handleLog(entry)
+}
+
+// handleLog 统一处理日志的输出
+func handleLog(entry *LogEntry) {
+ if ProgressBar != nil {
+ ProgressBar.Clear()
+ }
+
+ printLog(entry)
+
+ if ProgressBar != nil {
+ ProgressBar.RenderBlank()
+ }
+}
+
+// LogInfo 记录信息日志
+func LogInfo(msg string) {
+ handleLog(&LogEntry{
+ Level: LogLevelInfo,
+ Time: time.Now(),
+ Content: msg,
+ })
+}
+
+// LogSuccess 记录成功日志,并更新最后成功时间
+func LogSuccess(result string) {
+ entry := &LogEntry{
+ Level: LogLevelSuccess,
+ Time: time.Now(),
+ Content: result,
+ }
+
+ handleLog(entry)
+
+ // 更新最后成功时间
+ status.mu.Lock()
+ status.lastSuccess = time.Now()
+ status.mu.Unlock()
+}
+
+// LogDebug 记录调试日志
+func LogDebug(msg string) {
+ handleLog(&LogEntry{
+ Level: LogLevelDebug,
+ Time: time.Now(),
+ Content: msg,
+ })
+}
+
+// CheckErrs 检查是否为需要重试的错误
+func CheckErrs(err error) error {
+ if err == nil {
+ return nil
+ }
+
+ // 已知需要重试的错误列表
+ errs := []string{
+ "closed by the remote host", "too many connections",
+ "EOF", "A connection attempt failed",
+ "established connection failed", "connection attempt failed",
+ "Unable to read", "is not allowed to connect to this",
+ "no pg_hba.conf entry",
+ "No connection could be made",
+ "invalid packet size",
+ "bad connection",
+ }
+
+ // 检查错误是否匹配
+ errLower := strings.ToLower(err.Error())
+ for _, key := range errs {
+ if strings.Contains(errLower, strings.ToLower(key)) {
+ time.Sleep(3 * time.Second)
+ return err
+ }
+ }
+
+ return nil
+}
diff --git a/Common/Output.go b/Common/Output.go
new file mode 100644
index 0000000..b20dda5
--- /dev/null
+++ b/Common/Output.go
@@ -0,0 +1,245 @@
+package Common
+
+import (
+ "encoding/csv"
+ "encoding/json"
+ "fmt"
+ "os"
+ "path/filepath"
+ "strings"
+ "sync"
+ "time"
+)
+
+// 全局输出管理器
+var ResultOutput *OutputManager
+
+// OutputManager 输出管理器结构体
+type OutputManager struct {
+ mu sync.Mutex
+ outputPath string
+ outputFormat string
+ file *os.File
+ csvWriter *csv.Writer
+ jsonEncoder *json.Encoder
+ isInitialized bool
+}
+
+// ResultType 定义结果类型
+type ResultType string
+
+const (
+ HOST ResultType = "HOST" // 主机存活
+ PORT ResultType = "PORT" // 端口开放
+ SERVICE ResultType = "SERVICE" // 服务识别
+ VULN ResultType = "VULN" // 漏洞发现
+)
+
+// ScanResult 扫描结果结构
+type ScanResult struct {
+ Time time.Time `json:"time"` // 发现时间
+ Type ResultType `json:"type"` // 结果类型
+ Target string `json:"target"` // 目标(IP/域名/URL)
+ Status string `json:"status"` // 状态描述
+ Details map[string]interface{} `json:"details"` // 详细信息
+}
+
+// InitOutput 初始化输出系统
+func InitOutput() error {
+ LogDebug(GetText("output_init_start"))
+
+ // 验证输出格式
+ switch OutputFormat {
+ case "txt", "json", "csv":
+ // 有效的格式
+ default:
+ return fmt.Errorf(GetText("output_format_invalid"), OutputFormat)
+ }
+
+ // 验证输出路径
+ if Outputfile == "" {
+ return fmt.Errorf(GetText("output_path_empty"))
+ }
+
+ dir := filepath.Dir(Outputfile)
+ if err := os.MkdirAll(dir, 0755); err != nil {
+ LogDebug(GetText("output_create_dir_failed", err))
+ return fmt.Errorf(GetText("output_create_dir_failed", err))
+ }
+
+ manager := &OutputManager{
+ outputPath: Outputfile,
+ outputFormat: OutputFormat,
+ }
+
+ if err := manager.initialize(); err != nil {
+ LogDebug(GetText("output_init_failed", err))
+ return fmt.Errorf(GetText("output_init_failed", err))
+ }
+
+ ResultOutput = manager
+ LogDebug(GetText("output_init_success"))
+ return nil
+}
+
+func (om *OutputManager) initialize() error {
+ om.mu.Lock()
+ defer om.mu.Unlock()
+
+ if om.isInitialized {
+ LogDebug(GetText("output_already_init"))
+ return nil
+ }
+
+ LogDebug(GetText("output_opening_file", om.outputPath))
+ file, err := os.OpenFile(om.outputPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
+ if err != nil {
+ LogDebug(GetText("output_open_file_failed", err))
+ return fmt.Errorf(GetText("output_open_file_failed", err))
+ }
+ om.file = file
+
+ switch om.outputFormat {
+ case "csv":
+ LogDebug(GetText("output_init_csv"))
+ om.csvWriter = csv.NewWriter(file)
+ headers := []string{"Time", "Type", "Target", "Status", "Details"}
+ if err := om.csvWriter.Write(headers); err != nil {
+ LogDebug(GetText("output_write_csv_header_failed", err))
+ file.Close()
+ return fmt.Errorf(GetText("output_write_csv_header_failed", err))
+ }
+ om.csvWriter.Flush()
+ case "json":
+ LogDebug(GetText("output_init_json"))
+ om.jsonEncoder = json.NewEncoder(file)
+ om.jsonEncoder.SetIndent("", " ")
+ case "txt":
+ LogDebug(GetText("output_init_txt"))
+ default:
+ LogDebug(GetText("output_format_invalid", om.outputFormat))
+ }
+
+ om.isInitialized = true
+ LogDebug(GetText("output_init_complete"))
+ return nil
+}
+
+// SaveResult 保存扫描结果
+func SaveResult(result *ScanResult) error {
+ if ResultOutput == nil {
+ LogDebug(GetText("output_not_init"))
+ return fmt.Errorf(GetText("output_not_init"))
+ }
+
+ LogDebug(GetText("output_saving_result", result.Type, result.Target))
+ return ResultOutput.saveResult(result)
+}
+
+func (om *OutputManager) saveResult(result *ScanResult) error {
+ om.mu.Lock()
+ defer om.mu.Unlock()
+
+ if !om.isInitialized {
+ LogDebug(GetText("output_not_init"))
+ return fmt.Errorf(GetText("output_not_init"))
+ }
+
+ var err error
+ switch om.outputFormat {
+ case "txt":
+ err = om.writeTxt(result)
+ case "json":
+ err = om.writeJson(result)
+ case "csv":
+ err = om.writeCsv(result)
+ default:
+ LogDebug(GetText("output_format_invalid", om.outputFormat))
+ return fmt.Errorf(GetText("output_format_invalid", om.outputFormat))
+ }
+
+ if err != nil {
+ LogDebug(GetText("output_save_failed", err))
+ } else {
+ LogDebug(GetText("output_save_success", result.Type, result.Target))
+ }
+ return err
+}
+
+func (om *OutputManager) writeTxt(result *ScanResult) error {
+ // 格式化 Details 为键值对字符串
+ var details string
+ if len(result.Details) > 0 {
+ pairs := make([]string, 0, len(result.Details))
+ for k, v := range result.Details {
+ pairs = append(pairs, fmt.Sprintf("%s=%v", k, v))
+ }
+ details = strings.Join(pairs, ", ")
+ }
+
+ txt := GetText("output_txt_format",
+ result.Time.Format("2006-01-02 15:04:05"),
+ result.Type,
+ result.Target,
+ result.Status,
+ details,
+ ) + "\n"
+ _, err := om.file.WriteString(txt)
+ return err
+}
+
+func (om *OutputManager) writeJson(result *ScanResult) error {
+ return om.jsonEncoder.Encode(result)
+}
+
+func (om *OutputManager) writeCsv(result *ScanResult) error {
+ details, err := json.Marshal(result.Details)
+ if err != nil {
+ details = []byte("{}")
+ }
+
+ record := []string{
+ result.Time.Format("2006-01-02 15:04:05"),
+ string(result.Type),
+ result.Target,
+ result.Status,
+ string(details),
+ }
+
+ if err := om.csvWriter.Write(record); err != nil {
+ return err
+ }
+ om.csvWriter.Flush()
+ return om.csvWriter.Error()
+}
+
+// CloseOutput 关闭输出系统
+func CloseOutput() error {
+ if ResultOutput == nil {
+ LogDebug(GetText("output_no_need_close"))
+ return nil
+ }
+
+ LogDebug(GetText("output_closing"))
+ ResultOutput.mu.Lock()
+ defer ResultOutput.mu.Unlock()
+
+ if !ResultOutput.isInitialized {
+ LogDebug(GetText("output_no_need_close"))
+ return nil
+ }
+
+ if ResultOutput.csvWriter != nil {
+ LogDebug(GetText("output_flush_csv"))
+ ResultOutput.csvWriter.Flush()
+ }
+
+ if err := ResultOutput.file.Close(); err != nil {
+ LogDebug(GetText("output_close_failed", err))
+ return fmt.Errorf(GetText("output_close_failed", err))
+ }
+
+ ResultOutput.isInitialized = false
+ LogDebug(GetText("output_closed"))
+ return nil
+}
diff --git a/Common/Parse.go b/Common/Parse.go
new file mode 100644
index 0000000..6524819
--- /dev/null
+++ b/Common/Parse.go
@@ -0,0 +1,352 @@
+package Common
+
+import (
+ "bufio"
+ "encoding/hex"
+ "flag"
+ "fmt"
+ "net/url"
+ "os"
+ "strings"
+)
+
+func Parse(Info *HostInfo) error {
+ ParseUser()
+ ParsePass(Info)
+ if err := ParseInput(Info); err != nil {
+ return err
+ }
+ return nil
+}
+
+// ParseUser 解析用户名配置
+func ParseUser() error {
+ // 如果未指定用户名和用户名文件,直接返回
+ if Username == "" && UsersFile == "" {
+ return nil
+ }
+
+ var usernames []string
+
+ // 处理直接指定的用户名列表
+ if Username != "" {
+ usernames = strings.Split(Username, ",")
+ LogInfo(GetText("no_username_specified", len(usernames)))
+ }
+
+ // 从文件加载用户名列表
+ if UsersFile != "" {
+ users, err := Readfile(UsersFile)
+ if err != nil {
+ return fmt.Errorf("读取用户名文件失败: %v", err)
+ }
+
+ // 过滤空用户名
+ for _, user := range users {
+ if user != "" {
+ usernames = append(usernames, user)
+ }
+ }
+ LogInfo(GetText("load_usernames_from_file", len(users)))
+ }
+
+ // 去重处理
+ usernames = RemoveDuplicate(usernames)
+ LogInfo(GetText("total_usernames", 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
+ LogInfo(GetText("load_passwords", len(pwdList)))
+ }
+
+ // 从文件加载密码列表
+ if PasswordsFile != "" {
+ passes, err := Readfile(PasswordsFile)
+ if err != nil {
+ return fmt.Errorf("读取密码文件失败: %v", err)
+ }
+ for _, pass := range passes {
+ if pass != "" {
+ pwdList = append(pwdList, pass)
+ }
+ }
+ Passwords = pwdList
+ LogInfo(GetText("load_passwords_from_file", 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 {
+ HashValues = append(HashValues, line)
+ validCount++
+ } else {
+ LogError(GetText("invalid_hash", line))
+ }
+ }
+ LogInfo(GetText("load_valid_hashes", validCount))
+ }
+
+ // 处理直接指定的URL列表
+ if TargetURL != "" {
+ urls := strings.Split(TargetURL, ",")
+ tmpUrls := make(map[string]struct{})
+ for _, url := range urls {
+ if url != "" {
+ if _, ok := tmpUrls[url]; !ok {
+ tmpUrls[url] = struct{}{}
+ URLs = append(URLs, url)
+ }
+ }
+ }
+ LogInfo(GetText("load_urls", len(URLs)))
+ }
+
+ // 从文件加载URL列表
+ if URLsFile != "" {
+ urls, err := Readfile(URLsFile)
+ 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)
+ }
+ }
+ }
+ LogInfo(GetText("load_urls_from_file", len(urls)))
+ }
+
+ // 从文件加载主机列表
+ if HostsFile != "" {
+ hosts, err := Readfile(HostsFile)
+ if err != nil {
+ return fmt.Errorf("读取主机文件失败: %v", err)
+ }
+
+ tmpHosts := make(map[string]struct{})
+ for _, host := range hosts {
+ if host != "" {
+ if _, ok := tmpHosts[host]; !ok {
+ tmpHosts[host] = struct{}{}
+ if Info.Host == "" {
+ Info.Host = host
+ } else {
+ Info.Host += "," + host
+ }
+ }
+ }
+ }
+ LogInfo(GetText("load_hosts_from_file", len(hosts)))
+ }
+
+ // 从文件加载端口列表
+ if PortsFile != "" {
+ ports, err := Readfile(PortsFile)
+ 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()
+ LogInfo(GetText("load_ports_from_file"))
+ }
+
+ return nil
+}
+
+// Readfile 读取文件内容并返回非空行的切片
+func Readfile(filename string) ([]string, error) {
+ // 打开文件
+ file, err := os.Open(filename)
+ if err != nil {
+ LogError(GetText("open_file_failed", 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 {
+ LogError(GetText("read_file_failed", filename, err))
+ return nil, err
+ }
+
+ LogInfo(GetText("read_file_success", filename, lineCount))
+ return content, nil
+}
+
+// ParseInput 解析和验证输入参数配置
+func ParseInput(Info *HostInfo) error {
+ // 检查互斥的扫描模式
+ modes := 0
+ if Info.Host != "" || HostsFile != "" {
+ modes++
+ }
+ if TargetURL != "" || URLsFile != "" {
+ modes++
+ }
+ if LocalMode {
+ modes++
+ }
+
+ if modes == 0 {
+ // 无参数时显示帮助
+ flag.Usage()
+ return fmt.Errorf(GetText("specify_scan_params"))
+ } else if modes > 1 {
+ return fmt.Errorf(GetText("params_conflict"))
+ }
+
+ // 处理爆破线程配置
+ if BruteThreads <= 0 {
+ BruteThreads = 1
+ LogInfo(GetText("brute_threads", BruteThreads))
+ }
+
+ // 处理端口配置
+ if Ports == MainPorts {
+ Ports += "," + WebPorts
+ }
+
+ if AddPorts != "" {
+ if strings.HasSuffix(Ports, ",") {
+ Ports += AddPorts
+ } else {
+ Ports += "," + AddPorts
+ }
+ LogInfo(GetText("extra_ports", AddPorts))
+ }
+
+ // 处理用户名配置
+ if AddUsers != "" {
+ users := strings.Split(AddUsers, ",")
+ for dict := range Userdict {
+ Userdict[dict] = append(Userdict[dict], users...)
+ Userdict[dict] = RemoveDuplicate(Userdict[dict])
+ }
+ LogInfo(GetText("extra_usernames", AddUsers))
+ }
+
+ // 处理密码配置
+ if AddPasswords != "" {
+ passes := strings.Split(AddPasswords, ",")
+ Passwords = append(Passwords, passes...)
+ Passwords = RemoveDuplicate(Passwords)
+ LogInfo(GetText("extra_passwords", AddPasswords))
+ }
+
+ // 处理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(GetText("socks5_proxy_error", err))
+ }
+ DisablePing = true
+ LogInfo(GetText("socks5_proxy", Socks5Proxy))
+ }
+
+ // 处理HTTP代理配置
+ if HttpProxy != "" {
+ switch HttpProxy {
+ case "1":
+ HttpProxy = "http://127.0.0.1:8080"
+ case "2":
+ HttpProxy = "socks5://127.0.0.1:1080"
+ default:
+ if !strings.Contains(HttpProxy, "://") {
+ HttpProxy = "http://127.0.0.1:" + HttpProxy
+ }
+ }
+
+ if !strings.HasPrefix(HttpProxy, "socks") && !strings.HasPrefix(HttpProxy, "http") {
+ return fmt.Errorf(GetText("unsupported_proxy"))
+ }
+
+ _, err := url.Parse(HttpProxy)
+ if err != nil {
+ return fmt.Errorf(GetText("proxy_format_error", err))
+ }
+ LogInfo(GetText("http_proxy", HttpProxy))
+ }
+
+ // 处理Hash配置
+ if HashValue != "" {
+ if len(HashValue) != 32 {
+ return fmt.Errorf(GetText("hash_length_error"))
+ }
+ HashValues = append(HashValues, HashValue)
+ }
+
+ // 处理Hash列表
+ HashValues = RemoveDuplicate(HashValues)
+ for _, hash := range HashValues {
+ hashByte, err := hex.DecodeString(hash)
+ if err != nil {
+ LogError(GetText("hash_decode_failed", hash))
+ continue
+ }
+ HashBytes = append(HashBytes, hashByte)
+ }
+ HashValues = []string{}
+
+ return nil
+}
diff --git a/Common/ParseIP.go b/Common/ParseIP.go
new file mode 100644
index 0000000..d22471e
--- /dev/null
+++ b/Common/ParseIP.go
@@ -0,0 +1,334 @@
+package Common
+
+import (
+ "bufio"
+ "errors"
+ "fmt"
+ "math/rand"
+ "net"
+ "os"
+ "regexp"
+ "sort"
+ "strconv"
+ "strings"
+)
+
+var ParseIPErr = errors.New(GetText("parse_ip_error"))
+
+// ParseIP 解析IP地址配置
+func ParseIP(host string, filename string, nohosts ...string) (hosts []string, err error) {
+ // 处理主机和端口组合的情况
+ if filename == "" && strings.Contains(host, ":") {
+ hostport := strings.Split(host, ":")
+ if len(hostport) == 2 {
+ host = hostport[0]
+ hosts = ParseIPs(host)
+ Ports = hostport[1]
+ LogInfo(GetText("host_port_parsed", Ports))
+ }
+ } else {
+ // 解析主机地址
+ hosts = ParseIPs(host)
+
+ // 从文件加载额外主机
+ if filename != "" {
+ fileHosts, err := Readipfile(filename)
+ if err != nil {
+ LogError(GetText("read_host_file_failed", err))
+ } else {
+ hosts = append(hosts, fileHosts...)
+ LogInfo(GetText("extra_hosts_loaded", 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)
+ LogInfo(GetText("hosts_excluded", len(excludeHosts)))
+ }
+ }
+
+ // 去重处理
+ hosts = RemoveDuplicate(hosts)
+ LogInfo(GetText("final_valid_hosts", 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
+}
+
+func parseIP(ip string) []string {
+ reg := regexp.MustCompile(`[a-zA-Z]+`)
+
+ switch {
+ case ip == "192":
+ return parseIP("192.168.0.0/16")
+ case ip == "172":
+ return parseIP("172.16.0.0/12")
+ case ip == "10":
+ return parseIP("10.0.0.0/8")
+ case strings.HasSuffix(ip, "/8"):
+ return parseIP8(ip)
+ case strings.Contains(ip, "/"):
+ return parseIP2(ip)
+ case reg.MatchString(ip):
+ return []string{ip}
+ case strings.Contains(ip, "-"):
+ return parseIP1(ip)
+ default:
+ testIP := net.ParseIP(ip)
+ if testIP == nil {
+ LogError(GetText("invalid_ip_format", ip))
+ return nil
+ }
+ return []string{ip}
+ }
+}
+
+// parseIP2 解析CIDR格式的IP地址段
+func parseIP2(host string) []string {
+ _, ipNet, err := net.ParseCIDR(host)
+ if err != nil {
+ LogError(GetText("cidr_parse_failed", host, err))
+ return nil
+ }
+
+ ipRange := IPRange(ipNet)
+ hosts := parseIP1(ipRange)
+ LogInfo(GetText("parse_cidr_to_range", 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 {
+ LogError(GetText("ip_range_format_error", ip))
+ return nil
+ }
+
+ 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 {
+ LogError(GetText("invalid_ip_range", startNum, endNum))
+ return nil
+ }
+
+ for i := startNum; i <= endNum; i++ {
+ allIP = append(allIP, prefixIP+"."+strconv.Itoa(i))
+ }
+
+ LogInfo(GetText("generate_ip_range", prefixIP, startNum, prefixIP, endNum))
+ } else {
+ // 处理完整IP范围格式
+ splitIP1 := strings.Split(ipRange[0], ".")
+ splitIP2 := strings.Split(ipRange[1], ".")
+
+ if len(splitIP1) != 4 || len(splitIP2) != 4 {
+ LogError(GetText("ip_format_error", ip))
+ return nil
+ }
+
+ 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 {
+ LogError(GetText("invalid_ip_range", ipRange[0], ipRange[1]))
+ return nil
+ }
+ start[i], end[i] = ip1, ip2
+ }
+
+ 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)
+ }
+
+ LogInfo(GetText("generate_ip_range", ipRange[0], ipRange[1]))
+ }
+
+ return allIP
+}
+
+// IPRange 计算CIDR的起始IP和结束IP
+func IPRange(c *net.IPNet) string {
+ start := c.IP.String()
+ mask := c.Mask
+ bcst := make(net.IP, len(c.IP))
+ copy(bcst, c.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()
+
+ result := fmt.Sprintf("%s-%s", start, end)
+ LogInfo(GetText("cidr_range", result))
+ return result
+}
+
+// Readipfile 从文件中按行读取IP地址
+func Readipfile(filename string) ([]string, error) {
+ file, err := os.Open(filename)
+ if err != nil {
+ LogError(GetText("open_file_failed", filename, err))
+ return nil, err
+ }
+ defer file.Close()
+
+ var content []string
+ scanner := bufio.NewScanner(file)
+ scanner.Split(bufio.ScanLines)
+
+ for scanner.Scan() {
+ line := strings.TrimSpace(scanner.Text())
+ if line == "" {
+ continue
+ }
+
+ 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 {
+ LogError(GetText("invalid_port", line))
+ continue
+ }
+
+ hosts := ParseIPs(text[0])
+ for _, host := range hosts {
+ HostPort = append(HostPort, fmt.Sprintf("%s:%s", host, port))
+ }
+ LogInfo(GetText("parse_ip_port", line))
+ } else {
+ hosts := ParseIPs(line)
+ content = append(content, hosts...)
+ LogInfo(GetText("parse_ip_address", line))
+ }
+ }
+
+ if err := scanner.Err(); err != nil {
+ LogError(GetText("read_file_error", err))
+ return content, err
+ }
+
+ LogInfo(GetText("file_parse_complete", len(content)))
+ return content, nil
+}
+
+// RemoveDuplicate 对字符串切片进行去重
+func RemoveDuplicate(old []string) []string {
+ 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 {
+ LogError(GetText("invalid_ip_format", realIP))
+ return nil
+ }
+
+ // 获取/8网段的第一段
+ ipRange := strings.Split(ip, ".")[0]
+ var allIP []string
+
+ LogInfo(GetText("parse_subnet", 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)) // 广播地址前
+ }
+ }
+
+ LogInfo(GetText("sample_ip_generated", 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 50%
rename from common/ParsePort.go
rename to Common/ParsePort.go
index 4ccac9e..7dd5384 100644
--- a/common/ParsePort.go
+++ b/Common/ParsePort.go
@@ -1,32 +1,51 @@
-package common
+package Common
import (
"strconv"
"strings"
+ "sort"
)
-func ParsePort(ports string) (scanPorts []int) {
- if ports == "" {
- return
+// ParsePort 解析端口配置字符串为端口号列表
+func ParsePort(ports string) []int {
+ // 预定义的端口组
+ portGroups := map[string]string{
+ "service": ServicePorts,
+ "db": DbPorts,
+ "web": WebPorts,
+ "all": AllPorts,
+ "main": MainPorts,
}
+
+ // 检查是否匹配预定义组
+ if definedPorts, exists := portGroups[ports]; exists {
+ ports = definedPorts
+ }
+
+ if ports == "" {
+ 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)...)
- continue
- }
+
+ // 处理端口范围
upper := port
if strings.Contains(port, "-") {
ranges := strings.Split(port, "-")
if len(ranges) < 2 {
+ LogError(GetText("port_range_format_error", port))
continue
}
+ // 确保起始端口小于结束端口
startPort, _ := strconv.Atoi(ranges[0])
endPort, _ := strconv.Atoi(ranges[1])
if startPort < endPort {
@@ -37,27 +56,38 @@ 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 {
+ LogError(GetText("ignore_invalid_port", i))
continue
}
scanPorts = append(scanPorts, i)
}
}
+
+ // 去重并排序
scanPorts = removeDuplicate(scanPorts)
+ sort.Ints(scanPorts)
+
+ LogInfo(GetText("valid_port_count", len(scanPorts)))
return scanPorts
}
+// removeDuplicate 对整数切片进行去重
func removeDuplicate(old []int) []int {
- result := []int{}
- temp := map[int]struct{}{}
+ 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/ParseScanMode.go b/Common/ParseScanMode.go
new file mode 100644
index 0000000..786d5ce
--- /dev/null
+++ b/Common/ParseScanMode.go
@@ -0,0 +1,95 @@
+package Common
+
+// 扫描模式常量 - 使用大写开头表示这是一个预设的扫描模式
+const (
+ ModeAll = "All" // 全量扫描
+ ModeBasic = "Basic" // 基础扫描
+ ModeDatabase = "Database" // 数据库扫描
+ ModeWeb = "Web" // Web扫描
+ ModeService = "Service" // 服务扫描
+ ModeVul = "Vul" // 漏洞扫描
+ ModePort = "Port" // 端口扫描
+ ModeICMP = "ICMP" // ICMP探测
+ ModeLocal = "Local" // 本地信息收集
+)
+
+// 插件分类映射表 - 所有插件名使用小写
+var PluginGroups = map[string][]string{
+ ModeAll: {
+ "webtitle", "webpoc", // web类
+ "mysql", "mssql", "redis", "mongodb", "postgres", // 数据库类
+ "oracle", "memcached", "elasticsearch", "rabbitmq", "kafka", "activemq", "cassandra", "neo4j", // 数据库类
+ "ftp", "ssh", "telnet", "smb", "rdp", "vnc", "netbios", "ldap", "smtp", "imap", "pop3", "snmp", "modbus", "rsync", // 服务类
+ "ms17010", "smbghost", "smb2", // 漏洞类
+ "findnet", // 其他
+ },
+ ModeBasic: {
+ "webtitle", "ftp", "ssh", "smb", "findnet",
+ },
+ ModeDatabase: {
+ "mysql", "mssql", "redis", "mongodb",
+ "postgres", "oracle", "memcached", "elasticsearch", "rabbitmq", "kafka", "activemq", "cassandra", "neo4j",
+ },
+ ModeWeb: {
+ "webtitle", "webpoc",
+ },
+ ModeService: {
+ "ftp", "ssh", "telnet", "smb", "rdp", "vnc", "netbios", "ldap", "smtp", "imap", "pop3", "modbus", "rsync",
+ },
+ ModeVul: {
+ "ms17010", "smbghost", "smb2",
+ },
+ ModeLocal: {
+ "localinfo", "minidump", "dcinfo",
+ },
+}
+
+// ParseScanMode 解析扫描模式
+func ParseScanMode(mode string) {
+ LogInfo(GetText("parse_scan_mode", mode))
+
+ // 检查是否是预设模式
+ presetModes := []string{
+ ModeAll, ModeBasic, ModeDatabase, ModeWeb,
+ ModeService, ModeVul, ModePort, ModeICMP, ModeLocal,
+ }
+
+ for _, presetMode := range presetModes {
+ if mode == presetMode {
+ ScanMode = mode
+ if plugins := GetPluginsForMode(mode); plugins != nil {
+ LogInfo(GetText("using_preset_mode_plugins", mode, plugins))
+ } else {
+ LogInfo(GetText("using_preset_mode", mode))
+ }
+ return
+ }
+ }
+
+ // 检查是否是有效的插件名
+ if _, exists := PluginManager[mode]; exists {
+ ScanMode = mode
+ LogInfo(GetText("using_single_plugin", mode))
+ return
+ }
+
+ // 默认使用All模式
+ ScanMode = ModeAll
+ LogInfo(GetText("using_default_mode", ModeAll))
+ LogInfo(GetText("included_plugins", PluginGroups[ModeAll]))
+}
+
+// GetPluginsForMode 获取指定模式下的插件列表
+func GetPluginsForMode(mode string) []string {
+ plugins, exists := PluginGroups[mode]
+ if exists {
+ return plugins
+ }
+ return nil
+}
+
+// 辅助函数
+func IsPortScan() bool { return ScanMode == ModePort }
+func IsICMPScan() bool { return ScanMode == ModeICMP }
+func IsWebScan() bool { return ScanMode == ModeWeb }
+func GetScanMode() string { return ScanMode }
diff --git a/Common/Ports.go b/Common/Ports.go
new file mode 100644
index 0000000..cf5bbab
--- /dev/null
+++ b/Common/Ports.go
@@ -0,0 +1,23 @@
+package Common
+
+import (
+ "strconv"
+ "strings"
+)
+
+var ServicePorts = "21,22,23,25,110,135,139,143,162,389,445,465,502,587,636,873,993,995,1433,1521,2222,3306,3389,5020,5432,5672,5671,6379,8161,8443,9000,9092,9093,9200,10051,11211,15672,15671,27017,61616,61613"
+var DbPorts = "1433,1521,3306,5432,5672,6379,7687,9042,9093,9200,11211,27017,61616"
+var WebPorts = "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,8005,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,10051,10250,12018,12443,14000,15672,15671,16080,18000,18001,18002,18004,18008,18080,18082,18088,18090,18098,19001,20000,20720,20880,21000,21501,21502,28018"
+var AllPorts = "1-65535"
+var MainPorts = "21,22,23,80,81,110,135,139,143,389,443,445,502,873,993,995,1433,1521,3306,5432,5672,6379,7001,7687,8000,8005,8009,8080,8089,8443,9000,9042,9092,9200,10051,11211,15672,27017,61616"
+
+func ParsePortsFromString(portsStr string) []int {
+ var ports []int
+ portStrings := strings.Split(portsStr, ",")
+ for _, portStr := range portStrings {
+ if port, err := strconv.Atoi(portStr); err == nil {
+ ports = append(ports, port)
+ }
+ }
+ return ports
+}
diff --git a/Common/Proxy.go b/Common/Proxy.go
new file mode 100644
index 0000000..f70c198
--- /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(GetText("tcp_conn_failed"), err)
+ }
+ return conn, nil
+ }
+
+ // Socks5代理模式
+ dialer, err := Socks5Dialer(forward)
+ if err != nil {
+ return nil, fmt.Errorf(GetText("socks5_create_failed"), err)
+ }
+
+ conn, err := dialer.Dial(network, address)
+ if err != nil {
+ return nil, fmt.Errorf(GetText("socks5_conn_failed"), 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(GetText("socks5_parse_failed"), err)
+ }
+
+ // 验证代理类型
+ if strings.ToLower(u.Scheme) != "socks5" {
+ return nil, errors.New(GetText("socks5_only"))
+ }
+
+ 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(GetText("socks5_create_failed"), err)
+ }
+
+ return dialer, nil
+}
diff --git a/Common/Types.go b/Common/Types.go
new file mode 100644
index 0000000..3c36aea
--- /dev/null
+++ b/Common/Types.go
@@ -0,0 +1,40 @@
+// Config/types.go
+package Common
+
+type HostInfo struct {
+ Host string
+ Ports string
+ Url string
+ Infostr []string
+}
+
+// ScanPlugin 定义扫描插件的结构
+type ScanPlugin struct {
+ Name string // 插件名称
+ Ports []int // 关联的端口列表,空切片表示特殊扫描类型
+ ScanFunc func(*HostInfo) error // 扫描函数
+}
+
+// HasPort 检查插件是否支持指定端口
+func (p *ScanPlugin) HasPort(port int) bool {
+ // 如果没有指定端口列表,表示支持所有端口
+ if len(p.Ports) == 0 {
+ return true
+ }
+
+ // 检查端口是否在支持列表中
+ for _, supportedPort := range p.Ports {
+ if port == supportedPort {
+ return true
+ }
+ }
+ return false
+}
+
+// PluginManager 管理插件注册
+var PluginManager = make(map[string]ScanPlugin)
+
+// RegisterPlugin 注册插件
+func RegisterPlugin(name string, plugin ScanPlugin) {
+ PluginManager[name] = plugin
+}
diff --git a/Common/i18n.go b/Common/i18n.go
new file mode 100644
index 0000000..cc084b5
--- /dev/null
+++ b/Common/i18n.go
@@ -0,0 +1,1126 @@
+package Common
+
+import (
+ "fmt"
+ "strings"
+)
+
+// 支持的语言类型
+const (
+ LangZH = "zh" // 中文
+ LangEN = "en" // 英文
+ LangJA = "ja" // 日文
+ LangRU = "ru" // 俄文
+)
+
+// 多语言文本映射
+var i18nMap = map[string]map[string]string{
+ "output_init_start": {
+ LangZH: "开始初始化输出系统",
+ LangEN: "Starting output system initialization",
+ LangJA: "出力システムの初期化を開始",
+ LangRU: "Начало инициализации системы вывода",
+ },
+ "output_format_invalid": {
+ LangZH: "无效的输出格式: %s",
+ LangEN: "Invalid output format: %s",
+ LangJA: "無効な出力形式: %s",
+ LangRU: "Неверный формат вывода: %s",
+ },
+ "output_path_empty": {
+ LangZH: "输出路径不能为空",
+ LangEN: "Output path cannot be empty",
+ LangJA: "出力パスは空にできません",
+ LangRU: "Путь вывода не может быть пустым",
+ },
+ "output_create_dir_failed": {
+ LangZH: "创建输出目录失败: %v",
+ LangEN: "Failed to create output directory: %v",
+ LangJA: "出力ディレクトリの作成に失敗: %v",
+ LangRU: "Не удалось создать каталог вывода: %v",
+ },
+ "output_init_failed": {
+ LangZH: "初始化输出系统失败: %v",
+ LangEN: "Failed to initialize output system: %v",
+ LangJA: "出力システムの初期化に失敗: %v",
+ LangRU: "Не удалось инициализировать систему вывода: %v",
+ },
+ "output_init_success": {
+ LangZH: "输出系统初始化成功",
+ LangEN: "Output system initialized successfully",
+ LangJA: "出力システムの初期化に成功",
+ LangRU: "Система вывода успешно инициализирована",
+ },
+ "output_already_init": {
+ LangZH: "输出系统已经初始化",
+ LangEN: "Output system already initialized",
+ LangJA: "出力システムは既に初期化されています",
+ LangRU: "Система вывода уже инициализирована",
+ },
+ "output_opening_file": {
+ LangZH: "正在打开输出文件: %s",
+ LangEN: "Opening output file: %s",
+ LangJA: "出力ファイルを開いています: %s",
+ LangRU: "Открытие файла вывода: %s",
+ },
+ "output_open_file_failed": {
+ LangZH: "打开输出文件失败: %v",
+ LangEN: "Failed to open output file: %v",
+ LangJA: "出力ファイルを開くのに失敗: %v",
+ LangRU: "Не удалось открыть файл вывода: %v",
+ },
+ "output_init_csv": {
+ LangZH: "初始化CSV输出",
+ LangEN: "Initializing CSV output",
+ LangJA: "CSV出力を初期化中",
+ LangRU: "Инициализация вывода CSV",
+ },
+ "output_write_csv_header_failed": {
+ LangZH: "写入CSV头失败: %v",
+ LangEN: "Failed to write CSV header: %v",
+ LangJA: "CSVヘッダーの書き込みに失敗: %v",
+ LangRU: "Не удалось записать заголовок CSV: %v",
+ },
+ "output_init_json": {
+ LangZH: "初始化JSON输出",
+ LangEN: "Initializing JSON output",
+ LangJA: "JSON出力を初期化中",
+ LangRU: "Инициализация вывода JSON",
+ },
+ "output_init_txt": {
+ LangZH: "初始化文本输出",
+ LangEN: "Initializing text output",
+ LangJA: "テキスト出力を初期化中",
+ LangRU: "Инициализация текстового вывода",
+ },
+ "output_init_complete": {
+ LangZH: "输出系统初始化完成",
+ LangEN: "Output system initialization complete",
+ LangJA: "出力システムの初期化が完了",
+ LangRU: "Инициализация системы вывода завершена",
+ },
+ "output_not_init": {
+ LangZH: "输出系统未初始化",
+ LangEN: "Output system not initialized",
+ LangJA: "出力システムが初期化されていません",
+ LangRU: "Система вывода не инициализирована",
+ },
+ "output_saving_result": {
+ LangZH: "正在保存%s结果: %s",
+ LangEN: "Saving %s result: %s",
+ LangJA: "%s結果を保存中: %s",
+ LangRU: "Сохранение результата %s: %s",
+ },
+ "output_save_failed": {
+ LangZH: "保存结果失败: %v",
+ LangEN: "Failed to save result: %v",
+ LangJA: "結果の保存に失敗: %v",
+ LangRU: "Не удалось сохранить результат: %v",
+ },
+ "output_save_success": {
+ LangZH: "成功保存%s结果: %s",
+ LangEN: "Successfully saved %s result: %s",
+ LangJA: "%s結果の保存に成功: %s",
+ LangRU: "Успешно сохранен результат %s: %s",
+ },
+ "output_txt_format": {
+ LangZH: "[%s] [%s] 目标:%s 状态:%s 详情:%s",
+ LangEN: "[%s] [%s] Target:%s Status:%s Details:%s",
+ LangJA: "[%s] [%s] ターゲット:%s 状態:%s 詳細:%s",
+ LangRU: "[%s] [%s] Цель:%s Статус:%s Подробности:%s",
+ },
+ "output_no_need_close": {
+ LangZH: "输出系统无需关闭",
+ LangEN: "No need to close output system",
+ LangJA: "出力システムを閉じる必要はありません",
+ LangRU: "Нет необходимости закрывать систему вывода",
+ },
+ "output_closing": {
+ LangZH: "正在关闭输出系统",
+ LangEN: "Closing output system",
+ LangJA: "出力システムを閉じています",
+ LangRU: "Закрытие системы вывода",
+ },
+ "output_flush_csv": {
+ LangZH: "正在刷新CSV缓冲",
+ LangEN: "Flushing CSV buffer",
+ LangJA: "CSVバッファをフラッシュ中",
+ LangRU: "Очистка буфера CSV",
+ },
+ "output_close_failed": {
+ LangZH: "关闭输出文件失败: %v",
+ LangEN: "Failed to close output file: %v",
+ LangJA: "出力ファイルを閉じるのに失敗: %v",
+ LangRU: "Не удалось закрыть файл вывода: %v",
+ },
+ "output_closed": {
+ LangZH: "输出系统已关闭",
+ LangEN: "Output system closed",
+ LangJA: "出力システムが閉じられました",
+ LangRU: "Система вывода закрыта",
+ },
+ "flag_host": {
+ LangZH: "指定目标主机,支持以下格式:\n" +
+ " - 单个IP: 192.168.11.11\n" +
+ " - IP范围: 192.168.11.11-255\n" +
+ " - 多个IP: 192.168.11.11,192.168.11.12",
+
+ LangEN: "Specify target host, supports following formats:\n" +
+ " - Single IP: 192.168.11.11\n" +
+ " - IP Range: 192.168.11.11-255\n" +
+ " - Multiple IPs: 192.168.11.11,192.168.11.12",
+
+ LangJA: "ターゲットホストを指定、以下の形式をサポート:\n" +
+ " - 単一IP: 192.168.11.11\n" +
+ " - IP範囲: 192.168.11.11-255\n" +
+ " - 複数IP: 192.168.11.11,192.168.11.12",
+
+ LangRU: "Укажите целевой хост, поддерживаются следующие форматы:\n" +
+ " - Один IP: 192.168.11.11\n" +
+ " - Диапазон IP: 192.168.11.11-255\n" +
+ " - Несколько IP: 192.168.11.11,192.168.11.12",
+ },
+ "flag_ports": {
+ LangZH: "指定扫描端口,支持以下格式:\n" +
+ "格式:\n" +
+ " - 单个: 22\n" +
+ " - 范围: 1-65535\n" +
+ " - 多个: 22,80,3306\n" +
+ "预设组:\n" +
+ " - main: 常用端口组\n" +
+ " - service: 服务端口组\n" +
+ " - db: 数据库端口组\n" +
+ " - web: Web端口组\n" +
+ " - all: 全部端口\n" +
+ "示例: -p main, -p 80,443, -p 1-1000",
+
+ LangEN: "Specify scan ports, supports:\n" +
+ "Format:\n" +
+ " - Single: 22\n" +
+ " - Range: 1-65535\n" +
+ " - Multiple: 22,80,3306\n" +
+ "Presets:\n" +
+ " - main: Common ports\n" +
+ " - service: Service ports\n" +
+ " - db: Database ports\n" +
+ " - web: Web ports\n" +
+ " - all: All ports\n" +
+ "Example: -p main, -p 80,443, -p 1-1000",
+
+ LangJA: "スキャンポートを指定:\n" +
+ "形式:\n" +
+ " - 単一: 22\n" +
+ " - 範囲: 1-65535\n" +
+ " - 複数: 22,80,3306\n" +
+ "プリセット:\n" +
+ " - main: 一般ポート\n" +
+ " - service: サービスポート\n" +
+ " - db: データベースポート\n" +
+ " - web: Webポート\n" +
+ " - all: 全ポート\n" +
+ "例: -p main, -p 80,443, -p 1-1000",
+
+ LangRU: "Укажите порты сканирования:\n" +
+ "Формат:\n" +
+ " - Один: 22\n" +
+ " - Диапазон: 1-65535\n" +
+ " - Несколько: 22,80,3306\n" +
+ "Предустановки:\n" +
+ " - main: Общие порты\n" +
+ " - service: Порты служб\n" +
+ " - db: Порты баз данных\n" +
+ " - web: Web порты\n" +
+ " - all: Все порты\n" +
+ "Пример: -p main, -p 80,443, -p 1-1000",
+ },
+ "flag_scan_mode": {
+ LangZH: "指定扫描模式:\n" +
+ "预设模式:\n" +
+ " - All: 全量扫描\n" +
+ " - Basic: 基础扫描(Web/FTP/SSH等)\n" +
+ " - Database: 数据库扫描\n" +
+ " - Web: Web服务扫描\n" +
+ " - Service: 常见服务扫描\n" +
+ " - Vul: 漏洞扫描\n" +
+ " - Port: 端口扫描\n" +
+ " - ICMP: 存活探测\n" +
+ " - Local: 本地信息\n" +
+ "单项扫描:\n" +
+ " - web/db: mysql,redis等\n" +
+ " - service: ftp,ssh等\n" +
+ " - vul: ms17010等",
+
+ LangEN: "Specify scan mode:\n" +
+ "Preset modes:\n" +
+ " - All: Full scan\n" +
+ " - Basic: Basic scan(Web/FTP/SSH)\n" +
+ " - Database: Database scan\n" +
+ " - Web: Web service scan\n" +
+ " - Service: Common service scan\n" +
+ " - Vul: Vulnerability scan\n" +
+ " - Port: Port scan\n" +
+ " - ICMP: Alive detection\n" +
+ " - Local: Local info\n" +
+ "Single scan:\n" +
+ " - web/db: mysql,redis etc\n" +
+ " - service: ftp,ssh etc\n" +
+ " - vul: ms17010 etc",
+
+ LangJA: "スキャンモードを指定:\n" +
+ "プリセットモード:\n" +
+ " - All: フルスキャン\n" +
+ " - Basic: 基本スキャン(Web/FTP/SSH)\n" +
+ " - Database: データベーススキャン\n" +
+ " - Web: Webサービススキャン\n" +
+ " - Service: 一般サービススキャン\n" +
+ " - Vul: 脆弱性スキャン\n" +
+ " - Port: ポートスキャン\n" +
+ " - ICMP: 生存確認\n" +
+ " - Local: ローカル情報\n" +
+ "単一スキャン:\n" +
+ " - web/db: mysql,redis など\n" +
+ " - service: ftp,ssh など\n" +
+ " - vul: ms17010 など",
+
+ LangRU: "Укажите режим сканирования:\n" +
+ "Предустановки:\n" +
+ " - All: Полное сканирование\n" +
+ " - Basic: Базовое сканирование(Web/FTP/SSH)\n" +
+ " - Database: Сканирование БД\n" +
+ " - Web: Веб-сервисы\n" +
+ " - Service: Общие службы\n" +
+ " - Vul: Уязвимости\n" +
+ " - Port: Порты\n" +
+ " - ICMP: Обнаружение\n" +
+ " - Local: Локальная информация\n" +
+ "Одиночное сканирование:\n" +
+ " - web/db: mysql,redis и др\n" +
+ " - service: ftp,ssh и др\n" +
+ " - vul: ms17010 и др",
+ },
+ "flag_exclude_hosts": {
+ LangZH: "排除指定主机范围,支持CIDR格式,如: 192.168.1.1/24",
+ LangEN: "Exclude host ranges, supports CIDR format, e.g.: 192.168.1.1/24",
+ LangJA: "除外ホスト範囲を指定、CIDR形式対応、例: 192.168.1.1/24",
+ LangRU: "Исключить диапазоны хостов, поддерживает формат CIDR, например: 192.168.1.1/24",
+ },
+
+ "flag_add_users": {
+ LangZH: "在默认用户列表基础上添加自定义用户名",
+ LangEN: "Add custom usernames to default user list",
+ LangJA: "デフォルトユーザーリストにカスタムユーザー名を追加",
+ LangRU: "Добавить пользовательские имена к списку по умолчанию",
+ },
+
+ "flag_add_passwords": {
+ LangZH: "在默认密码列表基础上添加自定义密码",
+ LangEN: "Add custom passwords to default password list",
+ LangJA: "デフォルトパスワードリストにカスタムパスワードを追加",
+ LangRU: "Добавить пользовательские пароли к списку по умолчанию",
+ },
+
+ "flag_username": {
+ LangZH: "指定单个用户名",
+ LangEN: "Specify single username",
+ LangJA: "単一ユーザー名を指定",
+ LangRU: "Указать одно имя пользователя",
+ },
+
+ "flag_password": {
+ LangZH: "指定单个密码",
+ LangEN: "Specify single password",
+ LangJA: "単一パスワードを指定",
+ LangRU: "Указать один пароль",
+ },
+
+ "flag_domain": {
+ LangZH: "指定域名(仅用于SMB协议)",
+ LangEN: "Specify domain name (SMB protocol only)",
+ LangJA: "ドメイン名を指定(SMBプロトコルのみ)",
+ LangRU: "Указать доменное имя (только для протокола SMB)",
+ },
+
+ "flag_ssh_key": {
+ LangZH: "指定SSH私钥文件路径(默认为id_rsa)",
+ LangEN: "Specify SSH private key file path (default: id_rsa)",
+ LangJA: "SSH秘密鍵ファイルパスを指定(デフォルト: id_rsa)",
+ LangRU: "Указать путь к файлу приватного ключа SSH (по умолчанию: id_rsa)",
+ },
+
+ "flag_thread_num": {
+ LangZH: "设置扫描线程数",
+ LangEN: "Set number of scanning threads",
+ LangJA: "スキャンスレッド数を設定",
+ LangRU: "Установить количество потоков сканирования",
+ },
+
+ "flag_timeout": {
+ LangZH: "设置连接超时时间(单位:秒)",
+ LangEN: "Set connection timeout (in seconds)",
+ LangJA: "接続タイムアウトを設定(秒単位)",
+ LangRU: "Установить таймаут соединения (в секундах)",
+ },
+
+ "flag_live_top": {
+ LangZH: "仅显示指定数量的存活主机",
+ LangEN: "Show only specified number of alive hosts",
+ LangJA: "指定した数の生存ホストのみを表示",
+ LangRU: "Показать только указанное количество активных хостов",
+ },
+
+ "flag_disable_ping": {
+ LangZH: "禁用主机存活探测",
+ LangEN: "Disable host alive detection",
+ LangJA: "ホスト生存確認を無効化",
+ LangRU: "Отключить обнаружение активных хостов",
+ },
+
+ "flag_use_ping": {
+ LangZH: "使用系统ping命令替代ICMP探测",
+ LangEN: "Use system ping command instead of ICMP probe",
+ LangJA: "ICMPプローブの代わりにシステムpingコマンドを使用",
+ LangRU: "Использовать системную команду ping вместо ICMP-зондирования",
+ },
+
+ "flag_command": {
+ LangZH: "指定要执行的系统命令(支持ssh和wmiexec)",
+ LangEN: "Specify system command to execute (supports ssh and wmiexec)",
+ LangJA: "実行するシステムコマンドを指定(sshとwmiexecをサポート)",
+ LangRU: "Указать системную команду для выполнения (поддерживает ssh и wmiexec)",
+ },
+
+ "flag_skip_fingerprint": {
+ LangZH: "跳过端口指纹识别",
+ LangEN: "Skip port fingerprint identification",
+ LangJA: "ポートフィンガープリント識別をスキップ",
+ LangRU: "Пропустить идентификацию отпечатков портов",
+ },
+
+ "flag_hosts_file": {
+ LangZH: "从文件中读取目标主机列表",
+ LangEN: "Read target host list from file",
+ LangJA: "ファイルからターゲットホストリストを読み込む",
+ LangRU: "Чтение списка целевых хостов из файла",
+ },
+
+ "flag_users_file": {
+ LangZH: "从文件中读取用户名字典",
+ LangEN: "Read username dictionary from file",
+ LangJA: "ファイルからユーザー名辞書を読み込む",
+ LangRU: "Чтение словаря имен пользователей из файла",
+ },
+
+ "flag_passwords_file": {
+ LangZH: "从文件中读取密码字典",
+ LangEN: "Read password dictionary from file",
+ LangJA: "ファイルからパスワード辞書を読み込む",
+ LangRU: "Чтение словаря паролей из файла",
+ },
+
+ "flag_hash_file": {
+ LangZH: "从文件中读取Hash字典",
+ LangEN: "Read hash dictionary from file",
+ LangJA: "ファイルからハッシュ辞書を読み込む",
+ LangRU: "Чтение словаря хэшей из файла",
+ },
+
+ "flag_ports_file": {
+ LangZH: "从文件中读取端口列表",
+ LangEN: "Read port list from file",
+ LangJA: "ファイルからポートリストを読み込む",
+ LangRU: "Чтение списка портов из файла",
+ },
+
+ "flag_target_url": {
+ LangZH: "指定目标URL",
+ LangEN: "Specify target URL",
+ LangJA: "ターゲットURLを指定",
+ LangRU: "Указать целевой URL",
+ },
+
+ "flag_urls_file": {
+ LangZH: "从文件中读取URL列表",
+ LangEN: "Read URL list from file",
+ LangJA: "ファイルからURLリストを読み込む",
+ LangRU: "Чтение списка URL из файла",
+ },
+
+ "flag_cookie": {
+ LangZH: "设置HTTP请求Cookie",
+ LangEN: "Set HTTP request cookie",
+ LangJA: "HTTPリクエストのCookieを設定",
+ LangRU: "Установить cookie HTTP-запроса",
+ },
+
+ "flag_web_timeout": {
+ LangZH: "设置Web请求超时时间(单位:秒)",
+ LangEN: "Set Web request timeout (in seconds)",
+ LangJA: "Webリクエストタイムアウトを設定(秒単位)",
+ LangRU: "Установить таймаут веб-запроса (в секундах)",
+ },
+
+ "flag_http_proxy": {
+ LangZH: "设置HTTP代理服务器",
+ LangEN: "Set HTTP proxy server",
+ LangJA: "HTTPプロキシサーバーを設定",
+ LangRU: "Установить HTTP прокси-сервер",
+ },
+
+ "flag_socks5_proxy": {
+ LangZH: "设置Socks5代理(用于TCP连接,将影响超时设置)",
+ LangEN: "Set Socks5 proxy (for TCP connections, will affect timeout settings)",
+ LangJA: "Socks5プロキシを設定(TCP接続用、タイムアウト設定に影響します)",
+ LangRU: "Установить Socks5 прокси (для TCP соединений, влияет на настройки таймаута)",
+ },
+ "flag_local_mode": {
+ LangZH: "启用本地信息收集模式",
+ LangEN: "Enable local information gathering mode",
+ LangJA: "ローカル情報収集モードを有効化",
+ LangRU: "Включить режим сбора локальной информации",
+ },
+
+ // POC配置相关
+ "flag_poc_path": {
+ LangZH: "指定自定义POC文件路径",
+ LangEN: "Specify custom POC file path",
+ LangJA: "カスタムPOCファイルパスを指定",
+ LangRU: "Указать путь к пользовательскому файлу POC",
+ },
+
+ "flag_poc_name": {
+ LangZH: "指定要使用的POC名称,如: -pocname weblogic",
+ LangEN: "Specify POC name to use, e.g.: -pocname weblogic",
+ LangJA: "使用するPOC名を指定、例: -pocname weblogic",
+ LangRU: "Указать имя используемого POC, например: -pocname weblogic",
+ },
+
+ "flag_poc_full": {
+ LangZH: "启用完整POC扫描(如测试shiro全部100个key)",
+ LangEN: "Enable full POC scan (e.g. test all 100 shiro keys)",
+ LangJA: "完全POCスキャンを有効化(例: shiroの全100キーをテスト)",
+ LangRU: "Включить полное POC-сканирование (например, тест всех 100 ключей shiro)",
+ },
+
+ "flag_dns_log": {
+ LangZH: "启用dnslog进行漏洞验证",
+ LangEN: "Enable dnslog for vulnerability verification",
+ LangJA: "脆弱性検証にdnslogを有効化",
+ LangRU: "Включить dnslog для проверки уязвимостей",
+ },
+
+ "flag_poc_num": {
+ LangZH: "设置POC扫描并发数",
+ LangEN: "Set POC scan concurrency",
+ LangJA: "POCスキャンの同時実行数を設定",
+ LangRU: "Установить параллельность POC-сканирования",
+ },
+
+ // Redis配置相关
+ "flag_redis_file": {
+ LangZH: "指定Redis写入的SSH公钥文件",
+ LangEN: "Specify SSH public key file for Redis write",
+ LangJA: "Redis書き込み用のSSH公開鍵ファイルを指定",
+ LangRU: "Указать файл публичного ключа SSH для записи Redis",
+ },
+
+ "flag_redis_shell": {
+ LangZH: "指定Redis写入的计划任务内容",
+ LangEN: "Specify cron task content for Redis write",
+ LangJA: "Redis書き込み用のcronタスク内容を指定",
+ LangRU: "Указать содержимое cron-задачи для записи Redis",
+ },
+
+ "flag_disable_redis": {
+ LangZH: "禁用Redis安全检测",
+ LangEN: "Disable Redis security detection",
+ LangJA: "Redisセキュリティ検出を無効化",
+ LangRU: "Отключить обнаружение безопасности Redis",
+ },
+ // 暴力破解配置
+ "flag_disable_brute": {
+ LangZH: "禁用密码暴力破解",
+ LangEN: "Disable password brute force",
+ LangJA: "パスワードブルートフォースを無効化",
+ LangRU: "Отключить перебор паролей",
+ },
+
+ "flag_max_retries": {
+ LangZH: "设置最大重试次数",
+ LangEN: "Set maximum retry attempts",
+ LangJA: "最大再試行回数を設定",
+ LangRU: "Установить максимальное количество попыток",
+ },
+
+ // 其他配置
+ "flag_remote_path": {
+ LangZH: "指定FCG/SMB远程文件路径",
+ LangEN: "Specify FCG/SMB remote file path",
+ LangJA: "FCG/SMBリモートファイルパスを指定",
+ LangRU: "Указать удаленный путь к файлу FCG/SMB",
+ },
+
+ "flag_hash_value": {
+ LangZH: "指定要破解的Hash值",
+ LangEN: "Specify hash value to crack",
+ LangJA: "クラックするハッシュ値を指定",
+ LangRU: "Указать хэш-значение для взлома",
+ },
+
+ "flag_shellcode": {
+ LangZH: "指定MS17漏洞利用的shellcode",
+ LangEN: "Specify shellcode for MS17 exploit",
+ LangJA: "MS17エクスプロイト用のシェルコードを指定",
+ LangRU: "Указать шеллкод для эксплойта MS17",
+ },
+
+ "flag_enable_wmi": {
+ LangZH: "启用WMI协议扫描",
+ LangEN: "Enable WMI protocol scan",
+ LangJA: "WMIプロトコルスキャンを有効化",
+ LangRU: "Включить сканирование протокола WMI",
+ },
+
+ // 输出配置
+ "flag_output_file": {
+ LangZH: "指定结果输出文件名",
+ LangEN: "Specify output result filename",
+ LangJA: "結果出力ファイル名を指定",
+ LangRU: "Указать имя файла для вывода результатов",
+ },
+
+ "flag_output_format": {
+ LangZH: "指定输出格式 (txt/json/csv)",
+ LangEN: "Specify output format (txt/json/csv)",
+ LangJA: "出力形式を指定 (txt/json/csv)",
+ LangRU: "Указать формат вывода (txt/json/csv)",
+ },
+
+ "flag_disable_save": {
+ LangZH: "禁止保存扫描结果",
+ LangEN: "Disable saving scan results",
+ LangJA: "スキャン結果の保存を無効化",
+ LangRU: "Отключить сохранение результатов сканирования",
+ },
+
+ "flag_silent_mode": {
+ LangZH: "启用静默扫描模式(减少屏幕输出)",
+ LangEN: "Enable silent scan mode (reduce screen output)",
+ LangJA: "サイレントスキャンモードを有効化(画面出力を減らす)",
+ LangRU: "Включить тихий режим сканирования (уменьшить вывод на экран)",
+ },
+
+ "flag_no_color": {
+ LangZH: "禁用彩色输出显示",
+ LangEN: "Disable colored output display",
+ LangJA: "カラー出力表示を無効化",
+ LangRU: "Отключить цветной вывод",
+ },
+
+ "flag_json_format": {
+ LangZH: "以JSON格式输出结果",
+ LangEN: "Output results in JSON format",
+ LangJA: "結果をJSON形式で出力",
+ LangRU: "Вывести результаты в формате JSON",
+ },
+
+ "flag_log_level": {
+ LangZH: "日志输出级别(ALL/SUCCESS/ERROR/INFO/DEBUG)",
+ LangEN: "Log output level (ALL/SUCCESS/ERROR/INFO/DEBUG)",
+ LangJA: "ログ出力レベル(ALL/SUCCESS/ERROR/INFO/DEBUG)",
+ LangRU: "Уровень вывода журнала (ALL/SUCCESS/ERROR/INFO/DEBUG)",
+ },
+
+ "flag_show_progress": {
+ LangZH: "开启进度条显示",
+ LangEN: "Enable progress bar display",
+ LangJA: "プログレスバー表示を有効化",
+ LangRU: "Включить отображение индикатора выполнения",
+ },
+ "no_username_specified": {
+ LangZH: "加载用户名: %d 个",
+ LangEN: "Loaded usernames: %d",
+ LangJA: "ユーザー名を読み込み: %d 個",
+ LangRU: "Загружено имен пользователей: %d",
+ },
+ "load_usernames_from_file": {
+ LangZH: "从文件加载用户名: %d 个",
+ LangEN: "Loaded usernames from file: %d",
+ LangJA: "ファイルからユーザー名を読み込み: %d 個",
+ LangRU: "Загружено имен пользователей из файла: %d",
+ },
+ "total_usernames": {
+ LangZH: "用户名总数: %d 个",
+ LangEN: "Total usernames: %d",
+ LangJA: "ユーザー名の総数: %d 個",
+ LangRU: "Всего имен пользователей: %d",
+ },
+ "load_passwords": {
+ LangZH: "加载密码: %d 个",
+ LangEN: "Loaded passwords: %d",
+ LangJA: "パスワードを読み込み: %d 個",
+ LangRU: "Загружено паролей: %d",
+ },
+ "load_passwords_from_file": {
+ LangZH: "从文件加载密码: %d 个",
+ LangEN: "Loaded passwords from file: %d",
+ LangJA: "ファイルからパスワードを読み込み: %d 個",
+ LangRU: "Загружено паролей из файла: %d",
+ },
+ "invalid_hash": {
+ LangZH: "无效的哈希值: %s (长度!=32)",
+ LangEN: "Invalid hash: %s (length!=32)",
+ LangJA: "無効なハッシュ値: %s (長さ!=32)",
+ LangRU: "Недопустимый хэш: %s (длина!=32)",
+ },
+ "load_valid_hashes": {
+ LangZH: "加载有效哈希值: %d 个",
+ LangEN: "Loaded valid hashes: %d",
+ LangJA: "有効なハッシュ値を読み込み: %d 個",
+ LangRU: "Загружено допустимых хэшей: %d",
+ },
+ "load_urls": {
+ LangZH: "加载URL: %d 个",
+ LangEN: "Loaded URLs: %d",
+ LangJA: "URLを読み込み: %d 個",
+ LangRU: "Загружено URL: %d",
+ },
+ "load_urls_from_file": {
+ LangZH: "从文件加载URL: %d 个",
+ LangEN: "Loaded URLs from file: %d",
+ LangJA: "ファイルからURLを読み込み: %d 個",
+ LangRU: "Загружено URL из файла: %d",
+ },
+ "load_hosts_from_file": {
+ LangZH: "从文件加载主机: %d 个",
+ LangEN: "Loaded hosts from file: %d",
+ LangJA: "ファイルからホストを読み込み: %d 個",
+ LangRU: "Загружено хостов из файла: %d",
+ },
+ "load_ports_from_file": {
+ LangZH: "从文件加载端口配置",
+ LangEN: "Loaded ports from file",
+ LangJA: "ファイルからポート設定を読み込み",
+ LangRU: "Загружены порты из файла",
+ },
+ "open_file_failed": {
+ LangZH: "打开文件失败 %s: %v",
+ LangEN: "Failed to open file %s: %v",
+ LangJA: "ファイルを開けませんでした %s: %v",
+ LangRU: "Не удалось открыть файл %s: %v",
+ },
+ "read_file_failed": {
+ LangZH: "读取文件错误 %s: %v",
+ LangEN: "Error reading file %s: %v",
+ LangJA: "ファイル読み込みエラー %s: %v",
+ LangRU: "Ошибка чтения файла %s: %v",
+ },
+ "read_file_success": {
+ LangZH: "读取文件成功 %s: %d 行",
+ LangEN: "Successfully read file %s: %d lines",
+ LangJA: "ファイル読み込み成功 %s: %d 行",
+ LangRU: "Успешно прочитан файл %s: %d строк",
+ },
+ "specify_scan_params": {
+ LangZH: "请指定扫描参数",
+ LangEN: "Please specify scan parameters",
+ LangJA: "スキャンパラメータを指定してください",
+ LangRU: "Пожалуйста, укажите параметры сканирования",
+ },
+ "params_conflict": {
+ LangZH: "参数 -h、-u、-local 不能同时使用",
+ LangEN: "Parameters -h, -u, -local cannot be used simultaneously",
+ LangJA: "パラメータ -h、-u、-local は同時に使用できません",
+ LangRU: "Параметры -h, -u, -local нельзя использовать одновременно",
+ },
+ "brute_threads": {
+ LangZH: "暴力破解线程数: %d",
+ LangEN: "Brute force threads: %d",
+ LangJA: "ブルートフォーススレッド数: %d",
+ LangRU: "Потоков для брутфорса: %d",
+ },
+ "extra_ports": {
+ LangZH: "额外端口: %s",
+ LangEN: "Extra ports: %s",
+ LangJA: "追加ポート: %s",
+ LangRU: "Дополнительные порты: %s",
+ },
+ "extra_usernames": {
+ LangZH: "额外用户名: %s",
+ LangEN: "Extra usernames: %s",
+ LangJA: "追加ユーザー名: %s",
+ LangRU: "Дополнительные имена пользователей: %s",
+ },
+ "extra_passwords": {
+ LangZH: "额外密码: %s",
+ LangEN: "Extra passwords: %s",
+ LangJA: "追加パスワード: %s",
+ LangRU: "Дополнительные пароли: %s",
+ },
+ "socks5_proxy": {
+ LangZH: "Socks5代理: %s",
+ LangEN: "Socks5 proxy: %s",
+ LangJA: "Socks5プロキシ: %s",
+ LangRU: "Socks5 прокси: %s",
+ },
+ "socks5_proxy_error": {
+ LangZH: "Socks5代理格式错误: %v",
+ LangEN: "Invalid Socks5 proxy format: %v",
+ LangJA: "Socks5プロキシフォーマットエラー: %v",
+ LangRU: "Неверный формат Socks5 прокси: %v",
+ },
+ "http_proxy": {
+ LangZH: "HTTP代理: %s",
+ LangEN: "HTTP proxy: %s",
+ LangJA: "HTTPプロキシ: %s",
+ LangRU: "HTTP прокси: %s",
+ },
+ "unsupported_proxy": {
+ LangZH: "不支持的代理类型",
+ LangEN: "Unsupported proxy type",
+ LangJA: "サポートされていないプロキシタイプ",
+ LangRU: "Неподдерживаемый тип прокси",
+ },
+ "proxy_format_error": {
+ LangZH: "代理格式错误: %v",
+ LangEN: "Invalid proxy format: %v",
+ LangJA: "プロキシフォーマットエラー: %v",
+ LangRU: "Неверный формат прокси: %v",
+ },
+ "hash_length_error": {
+ LangZH: "Hash长度必须为32位",
+ LangEN: "Hash length must be 32 bits",
+ LangJA: "ハッシュ長は32ビットでなければなりません",
+ LangRU: "Длина хэша должна быть 32 бита",
+ },
+ "hash_decode_failed": {
+ LangZH: "Hash解码失败: %s",
+ LangEN: "Hash decode failed: %s",
+ LangJA: "ハッシュのデコードに失敗: %s",
+ LangRU: "Не удалось декодировать хэш: %s",
+ },
+ "parse_ip_error": {
+ LangZH: "主机解析错误\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 (最后一位简写范围)",
+
+ LangEN: "Host parsing error\n" +
+ "Supported formats: \n" +
+ "192.168.1.1 (Single IP)\n" +
+ "192.168.1.1/8 (8-bit subnet)\n" +
+ "192.168.1.1/16 (16-bit subnet)\n" +
+ "192.168.1.1/24 (24-bit subnet)\n" +
+ "192.168.1.1,192.168.1.2 (IP list)\n" +
+ "192.168.1.1-192.168.255.255 (IP range)\n" +
+ "192.168.1.1-255 (Last octet range)",
+
+ LangJA: "ホスト解析エラー\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 (最後のオクテット範囲)",
+
+ LangRU: "Ошибка разбора хоста\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 (Диапазон последнего октета)",
+ },
+ "host_port_parsed": {
+ LangZH: "已解析主机端口组合,端口设置为: %s",
+ LangEN: "Host port combination parsed, port set to: %s",
+ LangJA: "ホストポートの組み合わせを解析し、ポートを設定: %s",
+ LangRU: "Комбинация хост-порт разобрана, порт установлен на: %s",
+ },
+ "read_host_file_failed": {
+ LangZH: "读取主机文件失败: %v",
+ LangEN: "Failed to read host file: %v",
+ LangJA: "ホストファイルの読み取りに失敗: %v",
+ LangRU: "Не удалось прочитать файл хостов: %v",
+ },
+ "extra_hosts_loaded": {
+ LangZH: "从文件加载额外主机: %d 个",
+ LangEN: "Loaded extra hosts from file: %d",
+ LangJA: "ファイルから追加ホストを読み込み: %d",
+ LangRU: "Загружено дополнительных хостов из файла: %d",
+ },
+ "hosts_excluded": {
+ LangZH: "已排除指定主机: %d 个",
+ LangEN: "Excluded specified hosts: %d",
+ LangJA: "指定されたホストを除外: %d",
+ LangRU: "Исключено указанных хостов: %d",
+ },
+ "final_valid_hosts": {
+ LangZH: "最终有效主机数量: %d",
+ LangEN: "Final valid host count: %d",
+ LangJA: "最終的な有効ホスト数: %d",
+ LangRU: "Итоговое количество действительных хостов: %d",
+ },
+ "invalid_ip_format": {
+ LangZH: "无效的IP格式: %s",
+ LangEN: "Invalid IP format: %s",
+ LangJA: "無効なIP形式: %s",
+ LangRU: "Неверный формат IP: %s",
+ },
+ "cidr_parse_failed": {
+ LangZH: "CIDR格式解析失败: %s, %v",
+ LangEN: "CIDR format parse failed: %s, %v",
+ LangJA: "CIDR形式の解析に失敗: %s, %v",
+ LangRU: "Ошибка разбора формата CIDR: %s, %v",
+ },
+ "parse_cidr_to_range": {
+ LangZH: "解析CIDR %s -> IP范围 %s",
+ LangEN: "Parse CIDR %s -> IP range %s",
+ LangJA: "CIDR %s -> IP範囲 %s を解析",
+ LangRU: "Разбор CIDR %s -> диапазон IP %s",
+ },
+ "ip_range_format_error": {
+ LangZH: "IP范围格式错误: %s",
+ LangEN: "IP range format error: %s",
+ LangJA: "IP範囲形式エラー: %s",
+ LangRU: "Ошибка формата диапазона IP: %s",
+ },
+ "invalid_ip_range": {
+ LangZH: "IP范围无效: %d-%d",
+ LangEN: "Invalid IP range: %d-%d",
+ LangJA: "無効なIP範囲: %d-%d",
+ LangRU: "Недопустимый диапазон IP: %d-%d",
+ },
+ "generate_ip_range": {
+ LangZH: "生成IP范围: %s.%d - %s.%d",
+ LangEN: "Generate IP range: %s.%d - %s.%d",
+ LangJA: "IP範囲を生成: %s.%d - %s.%d",
+ LangRU: "Создание диапазона IP: %s.%d - %s.%d",
+ },
+ "ip_format_error": {
+ LangZH: "IP格式错误: %s",
+ LangEN: "IP format error: %s",
+ LangJA: "IP形式エラー: %s",
+ LangRU: "Ошибка формата IP: %s",
+ },
+ "cidr_range": {
+ LangZH: "CIDR范围: %s",
+ LangEN: "CIDR range: %s",
+ LangJA: "CIDR範囲: %s",
+ LangRU: "Диапазон CIDR: %s",
+ },
+ "invalid_port": {
+ LangZH: "忽略无效端口: %s",
+ LangEN: "Ignore invalid port: %s",
+ LangJA: "無効なポートを無視: %s",
+ LangRU: "Игнорирование недопустимого порта: %s",
+ },
+ "parse_ip_port": {
+ LangZH: "解析IP端口组合: %s",
+ LangEN: "Parse IP port combination: %s",
+ LangJA: "IPポートの組み合わせを解析: %s",
+ LangRU: "Разбор комбинации IP-порт: %s",
+ },
+ "parse_ip_address": {
+ LangZH: "解析IP地址: %s",
+ LangEN: "Parse IP address: %s",
+ LangJA: "IPアドレスを解析: %s",
+ LangRU: "Разбор IP-адреса: %s",
+ },
+ "read_file_error": {
+ LangZH: "读取文件错误: %v",
+ LangEN: "Read file error: %v",
+ LangJA: "ファイル読み取りエラー: %v",
+ LangRU: "Ошибка чтения файла: %v",
+ },
+ "file_parse_complete": {
+ LangZH: "从文件解析完成: %d 个IP地址",
+ LangEN: "File parsing complete: %d IP addresses",
+ LangJA: "ファイルの解析が完了: %d 個のIPアドレス",
+ LangRU: "Разбор файла завершен: %d IP-адресов",
+ },
+ "parse_subnet": {
+ LangZH: "解析网段: %s.0.0.0/8",
+ LangEN: "Parse subnet: %s.0.0.0/8",
+ LangJA: "サブネットを解析: %s.0.0.0/8",
+ LangRU: "Разбор подсети: %s.0.0.0/8",
+ },
+ "sample_ip_generated": {
+ LangZH: "生成采样IP: %d 个",
+ LangEN: "Generated sample IPs: %d",
+ LangJA: "サンプルIPを生成: %d 個",
+ LangRU: "Сгенерировано примеров IP: %d",
+ },
+ "port_range_format_error": {
+ LangZH: "端口范围格式错误: %s",
+ LangEN: "Invalid port range format: %s",
+ LangJA: "ポート範囲フォーマットエラー: %s",
+ LangRU: "Неверный формат диапазона портов: %s",
+ },
+ "ignore_invalid_port": {
+ LangZH: "忽略无效端口: %d",
+ LangEN: "Ignore invalid port: %d",
+ LangJA: "無効なポートを無視: %d",
+ LangRU: "Игнорирование недопустимого порта: %d",
+ },
+ "valid_port_count": {
+ LangZH: "有效端口数量: %d",
+ LangEN: "Valid port count: %d",
+ LangJA: "有効なポート数: %d",
+ LangRU: "Количество действительных портов: %d",
+ },
+ "parse_scan_mode": {
+ LangZH: "解析扫描模式: %s",
+ LangEN: "Parse scan mode: %s",
+ LangJA: "スキャンモードを解析: %s",
+ LangRU: "Разбор режима сканирования: %s",
+ },
+ "using_preset_mode": {
+ LangZH: "使用预设模式: %s",
+ LangEN: "Using preset mode: %s",
+ LangJA: "プリセットモードを使用: %s",
+ LangRU: "Использование предустановленного режима: %s",
+ },
+ "using_preset_mode_plugins": {
+ LangZH: "使用预设模式: %s, 包含插件: %v",
+ LangEN: "Using preset mode: %s, included plugins: %v",
+ LangJA: "プリセットモードを使用: %s, 含まれるプラグイン: %v",
+ LangRU: "Использование предустановленного режима: %s, включенные плагины: %v",
+ },
+ "using_single_plugin": {
+ LangZH: "使用单个插件: %s",
+ LangEN: "Using single plugin: %s",
+ LangJA: "単一のプラグインを使用: %s",
+ LangRU: "Использование одного плагина: %s",
+ },
+ "using_default_mode": {
+ LangZH: "未识别的模式,使用默认模式: %s",
+ LangEN: "Unrecognized mode, using default mode: %s",
+ LangJA: "認識できないモード、デフォルトモードを使用: %s",
+ LangRU: "Нераспознанный режим, использование режима по умолчанию: %s",
+ },
+ "included_plugins": {
+ LangZH: "包含插件: %v",
+ LangEN: "Included plugins: %v",
+ LangJA: "含まれるプラグイン: %v",
+ LangRU: "Включенные плагины: %v",
+ },
+ "tcp_conn_failed": {
+ LangZH: "建立TCP连接失败: %v",
+ LangEN: "Failed to establish TCP connection: %v",
+ LangJA: "TCP接続の確立に失敗しました: %v",
+ LangRU: "Не удалось установить TCP-соединение: %v",
+ },
+ "socks5_create_failed": {
+ LangZH: "创建Socks5代理失败: %v",
+ LangEN: "Failed to create Socks5 proxy: %v",
+ LangJA: "Socks5プロキシの作成に失敗しました: %v",
+ LangRU: "Не удалось создать прокси Socks5: %v",
+ },
+ "socks5_conn_failed": {
+ LangZH: "通过Socks5建立连接失败: %v",
+ LangEN: "Failed to establish connection through Socks5: %v",
+ LangJA: "Socks5経由での接続確立に失敗しました: %v",
+ LangRU: "Не удалось установить соединение через Socks5: %v",
+ },
+ "socks5_parse_failed": {
+ LangZH: "解析Socks5代理地址失败: %v",
+ LangEN: "Failed to parse Socks5 proxy address: %v",
+ LangJA: "Socks5プロキシアドレスの解析に失敗しました: %v",
+ LangRU: "Не удалось разобрать адрес прокси Socks5: %v",
+ },
+ "socks5_only": {
+ LangZH: "仅支持socks5代理",
+ LangEN: "Only socks5 proxy is supported",
+ LangJA: "socks5プロキシのみサポートされています",
+ LangRU: "Поддерживается только прокси socks5",
+ },
+ "flag_language": {
+ LangZH: "指定界面语言 (zh:中文, en:英文, ja:日文, ru:俄文)",
+ LangEN: "Specify interface language (zh:Chinese, en:English, ja:Japanese, ru:Russian)",
+ LangJA: "インターフェース言語を指定 (zh:中国語, en:英語, ja:日本語, ru:ロシア語)",
+ LangRU: "Указать язык интерфейса (zh:Китайский, en:Английский, ja:Японский, ru:Русский)",
+ },
+ "icmp_listen_failed": {
+ LangZH: "ICMP监听失败: %v",
+ LangEN: "ICMP listen failed: %v",
+ LangJA: "ICMPリッスンに失敗: %v",
+ LangRU: "Ошибка прослушивания ICMP: %v",
+ },
+ "trying_no_listen_icmp": {
+ LangZH: "正在尝试无监听ICMP探测...",
+ LangEN: "Trying ICMP probe without listening...",
+ LangJA: "リッスンなしICMP探知を試みています...",
+ LangRU: "Пробуем ICMP-зондирование без прослушивания...",
+ },
+ "icmp_connect_failed": {
+ LangZH: "ICMP连接失败: %v",
+ LangEN: "ICMP connection failed: %v",
+ LangJA: "ICMP接続に失敗: %v",
+ LangRU: "Ошибка подключения ICMP: %v",
+ },
+ "insufficient_privileges": {
+ LangZH: "当前用户权限不足,无法发送ICMP包",
+ LangEN: "Insufficient privileges to send ICMP packets",
+ LangJA: "ICMPパケットを送信する権限が不足しています",
+ LangRU: "Недостаточно прав для отправки ICMP-пакетов",
+ },
+ "switching_to_ping": {
+ LangZH: "切换为PING方式探测...",
+ LangEN: "Switching to PING probe...",
+ LangJA: "PING探知に切り替えています...",
+ LangRU: "Переключение на PING-зондирование...",
+ },
+ "subnet_16_alive": {
+ LangZH: "%s.0.0/16 存活主机数: %d",
+ LangEN: "%s.0.0/16 alive hosts: %d",
+ LangJA: "%s.0.0/16 生存ホスト数: %d",
+ LangRU: "%s.0.0/16 живых хостов: %d",
+ },
+ "subnet_24_alive": {
+ LangZH: "%s.0/24 存活主机数: %d",
+ LangEN: "%s.0/24 alive hosts: %d",
+ LangJA: "%s.0/24 生存ホスト数: %d",
+ LangRU: "%s.0/24 живых хостов: %d",
+ },
+ "target_alive": {
+ LangZH: "目标 %-15s 存活 (%s)",
+ LangEN: "Target %-15s is alive (%s)",
+ LangJA: "ターゲット %-15s は生存 (%s)",
+ LangRU: "Цель %-15s жива (%s)",
+ },
+}
+
+// 当前语言设置
+var currentLang = LangZH
+
+func SetLanguage() {
+ // 使用flag设置的语言
+ switch strings.ToLower(Language) {
+ case LangZH, LangEN, LangJA, LangRU:
+ currentLang = strings.ToLower(Language)
+ default:
+ currentLang = LangEN // 不支持的语言默认使用英文
+ }
+}
+
+// GetText 获取指定key的当前语言文本
+func GetText(key string, args ...interface{}) string {
+ if texts, ok := i18nMap[key]; ok {
+ if text, ok := texts[currentLang]; ok {
+ if len(args) > 0 {
+ return fmt.Sprintf(text, args...)
+ }
+ return text
+ }
+ }
+ return key
+}
diff --git a/Core/ICMP.go b/Core/ICMP.go
new file mode 100644
index 0000000..a0227f3
--- /dev/null
+++ b/Core/ICMP.go
@@ -0,0 +1,429 @@
+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
+}
+
+// IsContain 检查切片中是否包含指定元素
+func IsContain(items []string, item string) bool {
+ for _, eachItem := range items {
+ if eachItem == item {
+ return true
+ }
+ }
+ return false
+}
+
+func handleAliveHosts(chanHosts chan string, hostslist []string, isPing bool) {
+ for ip := range chanHosts {
+ if _, ok := ExistHosts[ip]; !ok && IsContain(hostslist, ip) {
+ ExistHosts[ip] = struct{}{}
+ AliveHosts = append(AliveHosts, ip)
+
+ // 使用Output系统保存存活主机信息
+ protocol := "ICMP"
+ if isPing {
+ protocol = "PING"
+ }
+
+ result := &Common.ScanResult{
+ Time: time.Now(),
+ Type: Common.HOST,
+ Target: ip,
+ Status: "alive",
+ Details: map[string]interface{}{
+ "protocol": protocol,
+ },
+ }
+ Common.SaveResult(result)
+
+ // 保留原有的控制台输出
+ if !Common.Silent {
+ Common.LogSuccess(Common.GetText("target_alive", ip, protocol))
+ }
+ }
+ 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(Common.GetText("icmp_listen_failed", err))
+ Common.LogInfo(Common.GetText("trying_no_listen_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(Common.GetText("icmp_connect_failed", err))
+ Common.LogInfo(Common.GetText("insufficient_privileges"))
+ Common.LogInfo(Common.GetText("switching_to_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++ {
+ Common.LogSuccess(Common.GetText("subnet_16_alive", arrTop[i], arrLen[i]))
+ }
+ }
+
+ // 输出 /24 网段统计
+ if len(hostslist) > 256 {
+ arrTop, arrLen := ArrayCountValueTop(AliveHosts, Common.LiveTop, false)
+ for i := 0; i < len(arrTop); i++ {
+ Common.LogSuccess(Common.GetText("subnet_24_alive", arrTop[i], arrLen[i]))
+ }
+ }
+}
+
+// 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/PortFinger.go b/Core/PortFinger.go
new file mode 100644
index 0000000..a735620
--- /dev/null
+++ b/Core/PortFinger.go
@@ -0,0 +1,877 @@
+package Core
+
+import (
+ _ "embed"
+ "encoding/hex"
+ "fmt"
+ "github.com/shadow1ng/fscan/Common"
+ "regexp"
+ "strconv"
+ "strings"
+)
+
+//go:embed nmap-service-probes.txt
+var ProbeString string
+
+var v VScan // 改为VScan类型而不是指针
+
+type VScan struct {
+ Exclude string
+ AllProbes []Probe
+ UdpProbes []Probe
+ Probes []Probe
+ ProbesMapKName map[string]Probe
+}
+
+type Probe struct {
+ Name string // 探测器名称
+ Data string // 探测数据
+ Protocol string // 协议
+ Ports string // 端口范围
+ SSLPorts string // SSL端口范围
+
+ TotalWaitMS int // 总等待时间
+ TCPWrappedMS int // TCP包装等待时间
+ Rarity int // 稀有度
+ Fallback string // 回退探测器名称
+
+ Matchs *[]Match // 匹配规则列表
+}
+
+type Match struct {
+ IsSoft bool // 是否为软匹配
+ Service string // 服务名称
+ Pattern string // 匹配模式
+ VersionInfo string // 版本信息格式
+ FoundItems []string // 找到的项目
+ PatternCompiled *regexp.Regexp // 编译后的正则表达式
+}
+
+type Directive struct {
+ DirectiveName string
+ Flag string
+ Delimiter string
+ DirectiveStr string
+}
+
+type Extras struct {
+ VendorProduct string
+ Version string
+ Info string
+ Hostname string
+ OperatingSystem string
+ DeviceType string
+ CPE string
+}
+
+func init() {
+ Common.LogDebug("开始初始化全局变量")
+
+ v = VScan{} // 直接初始化VScan结构体
+ v.Init()
+
+ // 获取并检查 NULL 探测器
+ if nullProbe, ok := v.ProbesMapKName["NULL"]; ok {
+ Common.LogDebug(fmt.Sprintf("成功获取NULL探测器,Data长度: %d", len(nullProbe.Data)))
+ null = &nullProbe
+ } else {
+ Common.LogDebug("警告: 未找到NULL探测器")
+ }
+
+ // 获取并检查 GenericLines 探测器
+ if commonProbe, ok := v.ProbesMapKName["GenericLines"]; ok {
+ Common.LogDebug(fmt.Sprintf("成功获取GenericLines探测器,Data长度: %d", len(commonProbe.Data)))
+ common = &commonProbe
+ } else {
+ Common.LogDebug("警告: 未找到GenericLines探测器")
+ }
+
+ Common.LogDebug("全局变量初始化完成")
+}
+
+// 解析指令语法,返回指令结构
+func (p *Probe) getDirectiveSyntax(data string) (directive Directive) {
+ Common.LogDebug("开始解析指令语法,输入数据: " + data)
+
+ directive = Directive{}
+ // 查找第一个空格的位置
+ blankIndex := strings.Index(data, " ")
+ if blankIndex == -1 {
+ Common.LogDebug("未找到空格分隔符")
+ return directive
+ }
+
+ // 解析各个字段
+ directiveName := data[:blankIndex]
+ Flag := data[blankIndex+1 : blankIndex+2]
+ delimiter := data[blankIndex+2 : blankIndex+3]
+ directiveStr := data[blankIndex+3:]
+
+ directive.DirectiveName = directiveName
+ directive.Flag = Flag
+ directive.Delimiter = delimiter
+ directive.DirectiveStr = directiveStr
+
+ Common.LogDebug(fmt.Sprintf("指令解析结果: 名称=%s, 标志=%s, 分隔符=%s, 内容=%s",
+ directiveName, Flag, delimiter, directiveStr))
+
+ return directive
+}
+
+// 解析探测器信息
+func (p *Probe) parseProbeInfo(probeStr string) {
+ Common.LogDebug("开始解析探测器信息,输入字符串: " + probeStr)
+
+ // 提取协议和其他信息
+ proto := probeStr[:4]
+ other := probeStr[4:]
+
+ // 验证协议类型
+ if !(proto == "TCP " || proto == "UDP ") {
+ errMsg := "探测器协议必须是 TCP 或 UDP"
+ Common.LogDebug("错误: " + errMsg)
+ panic(errMsg)
+ }
+
+ // 验证其他信息不为空
+ if len(other) == 0 {
+ errMsg := "nmap-service-probes - 探测器名称无效"
+ Common.LogDebug("错误: " + errMsg)
+ panic(errMsg)
+ }
+
+ // 解析指令
+ directive := p.getDirectiveSyntax(other)
+
+ // 设置探测器属性
+ p.Name = directive.DirectiveName
+ p.Data = strings.Split(directive.DirectiveStr, directive.Delimiter)[0]
+ p.Protocol = strings.ToLower(strings.TrimSpace(proto))
+
+ Common.LogDebug(fmt.Sprintf("探测器解析完成: 名称=%s, 数据=%s, 协议=%s",
+ p.Name, p.Data, p.Protocol))
+}
+
+// 从字符串解析探测器信息
+func (p *Probe) fromString(data string) error {
+ Common.LogDebug("开始解析探测器字符串数据")
+ var err error
+
+ // 预处理数据
+ data = strings.TrimSpace(data)
+ lines := strings.Split(data, "\n")
+ if len(lines) == 0 {
+ return fmt.Errorf("输入数据为空")
+ }
+
+ probeStr := lines[0]
+ p.parseProbeInfo(probeStr)
+
+ // 解析匹配规则和其他配置
+ var matchs []Match
+ for _, line := range lines {
+ Common.LogDebug("处理行: " + line)
+ switch {
+ case strings.HasPrefix(line, "match "):
+ match, err := p.getMatch(line)
+ if err != nil {
+ Common.LogDebug("解析match失败: " + err.Error())
+ continue
+ }
+ matchs = append(matchs, match)
+
+ case strings.HasPrefix(line, "softmatch "):
+ softMatch, err := p.getSoftMatch(line)
+ if err != nil {
+ Common.LogDebug("解析softmatch失败: " + err.Error())
+ continue
+ }
+ matchs = append(matchs, softMatch)
+
+ case strings.HasPrefix(line, "ports "):
+ p.parsePorts(line)
+
+ case strings.HasPrefix(line, "sslports "):
+ p.parseSSLPorts(line)
+
+ case strings.HasPrefix(line, "totalwaitms "):
+ p.parseTotalWaitMS(line)
+
+ case strings.HasPrefix(line, "tcpwrappedms "):
+ p.parseTCPWrappedMS(line)
+
+ case strings.HasPrefix(line, "rarity "):
+ p.parseRarity(line)
+
+ case strings.HasPrefix(line, "fallback "):
+ p.parseFallback(line)
+ }
+ }
+ p.Matchs = &matchs
+ Common.LogDebug(fmt.Sprintf("解析完成,共有 %d 个匹配规则", len(matchs)))
+ return err
+}
+
+// 解析端口配置
+func (p *Probe) parsePorts(data string) {
+ p.Ports = data[len("ports")+1:]
+ Common.LogDebug("解析端口: " + p.Ports)
+}
+
+// 解析SSL端口配置
+func (p *Probe) parseSSLPorts(data string) {
+ p.SSLPorts = data[len("sslports")+1:]
+ Common.LogDebug("解析SSL端口: " + p.SSLPorts)
+}
+
+// 解析总等待时间
+func (p *Probe) parseTotalWaitMS(data string) {
+ waitMS, err := strconv.Atoi(strings.TrimSpace(data[len("totalwaitms")+1:]))
+ if err != nil {
+ Common.LogDebug("解析总等待时间失败: " + err.Error())
+ return
+ }
+ p.TotalWaitMS = waitMS
+ Common.LogDebug(fmt.Sprintf("总等待时间: %d ms", waitMS))
+}
+
+// 解析TCP包装等待时间
+func (p *Probe) parseTCPWrappedMS(data string) {
+ wrappedMS, err := strconv.Atoi(strings.TrimSpace(data[len("tcpwrappedms")+1:]))
+ if err != nil {
+ Common.LogDebug("解析TCP包装等待时间失败: " + err.Error())
+ return
+ }
+ p.TCPWrappedMS = wrappedMS
+ Common.LogDebug(fmt.Sprintf("TCP包装等待时间: %d ms", wrappedMS))
+}
+
+// 解析稀有度
+func (p *Probe) parseRarity(data string) {
+ rarity, err := strconv.Atoi(strings.TrimSpace(data[len("rarity")+1:]))
+ if err != nil {
+ Common.LogDebug("解析稀有度失败: " + err.Error())
+ return
+ }
+ p.Rarity = rarity
+ Common.LogDebug(fmt.Sprintf("稀有度: %d", rarity))
+}
+
+// 解析回退配置
+func (p *Probe) parseFallback(data string) {
+ p.Fallback = data[len("fallback")+1:]
+ Common.LogDebug("回退配置: " + p.Fallback)
+}
+
+// 判断是否为十六进制编码
+func isHexCode(b []byte) bool {
+ matchRe := regexp.MustCompile(`\\x[0-9a-fA-F]{2}`)
+ return matchRe.Match(b)
+}
+
+// 判断是否为八进制编码
+func isOctalCode(b []byte) bool {
+ matchRe := regexp.MustCompile(`\\[0-7]{1,3}`)
+ return matchRe.Match(b)
+}
+
+// 判断是否为结构化转义字符
+func isStructCode(b []byte) bool {
+ matchRe := regexp.MustCompile(`\\[aftnrv]`)
+ return matchRe.Match(b)
+}
+
+// 判断是否为正则表达式特殊字符
+func isReChar(n int64) bool {
+ reChars := `.*?+{}()^$|\`
+ for _, char := range reChars {
+ if n == int64(char) {
+ return true
+ }
+ }
+ return false
+}
+
+// 判断是否为其他转义序列
+func isOtherEscapeCode(b []byte) bool {
+ matchRe := regexp.MustCompile(`\\[^\\]`)
+ return matchRe.Match(b)
+}
+
+// 从内容解析探测器规则
+func (v *VScan) parseProbesFromContent(content string) {
+ Common.LogDebug("开始解析探测器规则文件内容")
+ var probes []Probe
+ var lines []string
+
+ // 过滤注释和空行
+ linesTemp := strings.Split(content, "\n")
+ for _, lineTemp := range linesTemp {
+ lineTemp = strings.TrimSpace(lineTemp)
+ if lineTemp == "" || strings.HasPrefix(lineTemp, "#") {
+ continue
+ }
+ lines = append(lines, lineTemp)
+ }
+
+ // 验证文件内容
+ if len(lines) == 0 {
+ errMsg := "读取nmap-service-probes文件失败: 内容为空"
+ Common.LogDebug("错误: " + errMsg)
+ panic(errMsg)
+ }
+
+ // 检查Exclude指令
+ excludeCount := 0
+ for _, line := range lines {
+ if strings.HasPrefix(line, "Exclude ") {
+ excludeCount++
+ }
+ if excludeCount > 1 {
+ errMsg := "nmap-service-probes文件中只允许有一个Exclude指令"
+ Common.LogDebug("错误: " + errMsg)
+ panic(errMsg)
+ }
+ }
+
+ // 验证第一行格式
+ firstLine := lines[0]
+ if !(strings.HasPrefix(firstLine, "Exclude ") || strings.HasPrefix(firstLine, "Probe ")) {
+ errMsg := "解析错误: 首行必须以\"Probe \"或\"Exclude \"开头"
+ Common.LogDebug("错误: " + errMsg)
+ panic(errMsg)
+ }
+
+ // 处理Exclude指令
+ if excludeCount == 1 {
+ v.Exclude = firstLine[len("Exclude")+1:]
+ lines = lines[1:]
+ Common.LogDebug("解析到Exclude规则: " + v.Exclude)
+ }
+
+ // 合并内容并分割探测器
+ content = "\n" + strings.Join(lines, "\n")
+ probeParts := strings.Split(content, "\nProbe")[1:]
+
+ // 解析每个探测器
+ for _, probePart := range probeParts {
+ probe := Probe{}
+ if err := probe.fromString(probePart); err != nil {
+ Common.LogDebug(fmt.Sprintf("解析探测器失败: %v", err))
+ continue
+ }
+ probes = append(probes, probe)
+ }
+
+ v.AllProbes = probes
+ Common.LogDebug(fmt.Sprintf("成功解析 %d 个探测器规则", len(probes)))
+}
+
+// 将探测器转换为名称映射
+func (v *VScan) parseProbesToMapKName() {
+ Common.LogDebug("开始构建探测器名称映射")
+ v.ProbesMapKName = map[string]Probe{}
+ for _, probe := range v.AllProbes {
+ v.ProbesMapKName[probe.Name] = probe
+ Common.LogDebug("添加探测器映射: " + probe.Name)
+ }
+}
+
+// 设置使用的探测器
+func (v *VScan) SetusedProbes() {
+ Common.LogDebug("开始设置要使用的探测器")
+
+ for _, probe := range v.AllProbes {
+ if strings.ToLower(probe.Protocol) == "tcp" {
+ if probe.Name == "SSLSessionReq" {
+ Common.LogDebug("跳过 SSLSessionReq 探测器")
+ continue
+ }
+
+ v.Probes = append(v.Probes, probe)
+ Common.LogDebug("添加TCP探测器: " + probe.Name)
+
+ // 特殊处理TLS会话请求
+ if probe.Name == "TLSSessionReq" {
+ sslProbe := v.ProbesMapKName["SSLSessionReq"]
+ v.Probes = append(v.Probes, sslProbe)
+ Common.LogDebug("为TLSSessionReq添加SSL探测器")
+ }
+ } else {
+ v.UdpProbes = append(v.UdpProbes, probe)
+ Common.LogDebug("添加UDP探测器: " + probe.Name)
+ }
+ }
+
+ Common.LogDebug(fmt.Sprintf("探测器设置完成,TCP: %d个, UDP: %d个",
+ len(v.Probes), len(v.UdpProbes)))
+}
+
+// 解析match指令获取匹配规则
+func (p *Probe) getMatch(data string) (match Match, err error) {
+ Common.LogDebug("开始解析match指令:" + data)
+ match = Match{}
+
+ // 提取match文本并解析指令语法
+ matchText := data[len("match")+1:]
+ directive := p.getDirectiveSyntax(matchText)
+
+ // 分割文本获取pattern和版本信息
+ textSplited := strings.Split(directive.DirectiveStr, directive.Delimiter)
+ if len(textSplited) == 0 {
+ return match, fmt.Errorf("无效的match指令格式")
+ }
+
+ pattern := textSplited[0]
+ versionInfo := strings.Join(textSplited[1:], "")
+
+ // 解码并编译正则表达式
+ patternUnescaped, decodeErr := DecodePattern(pattern)
+ if decodeErr != nil {
+ Common.LogDebug("解码pattern失败: " + decodeErr.Error())
+ return match, decodeErr
+ }
+
+ patternUnescapedStr := string([]rune(string(patternUnescaped)))
+ patternCompiled, compileErr := regexp.Compile(patternUnescapedStr)
+ if compileErr != nil {
+ Common.LogDebug("编译正则表达式失败: " + compileErr.Error())
+ return match, compileErr
+ }
+
+ // 设置match对象属性
+ match.Service = directive.DirectiveName
+ match.Pattern = pattern
+ match.PatternCompiled = patternCompiled
+ match.VersionInfo = versionInfo
+
+ Common.LogDebug(fmt.Sprintf("解析match成功: 服务=%s, Pattern=%s",
+ match.Service, match.Pattern))
+ return match, nil
+}
+
+// 解析softmatch指令获取软匹配规则
+func (p *Probe) getSoftMatch(data string) (softMatch Match, err error) {
+ Common.LogDebug("开始解析softmatch指令:" + data)
+ softMatch = Match{IsSoft: true}
+
+ // 提取softmatch文本并解析指令语法
+ matchText := data[len("softmatch")+1:]
+ directive := p.getDirectiveSyntax(matchText)
+
+ // 分割文本获取pattern和版本信息
+ textSplited := strings.Split(directive.DirectiveStr, directive.Delimiter)
+ if len(textSplited) == 0 {
+ return softMatch, fmt.Errorf("无效的softmatch指令格式")
+ }
+
+ pattern := textSplited[0]
+ versionInfo := strings.Join(textSplited[1:], "")
+
+ // 解码并编译正则表达式
+ patternUnescaped, decodeErr := DecodePattern(pattern)
+ if decodeErr != nil {
+ Common.LogDebug("解码pattern失败: " + decodeErr.Error())
+ return softMatch, decodeErr
+ }
+
+ patternUnescapedStr := string([]rune(string(patternUnescaped)))
+ patternCompiled, compileErr := regexp.Compile(patternUnescapedStr)
+ if compileErr != nil {
+ Common.LogDebug("编译正则表达式失败: " + compileErr.Error())
+ return softMatch, compileErr
+ }
+
+ // 设置softMatch对象属性
+ softMatch.Service = directive.DirectiveName
+ softMatch.Pattern = pattern
+ softMatch.PatternCompiled = patternCompiled
+ softMatch.VersionInfo = versionInfo
+
+ Common.LogDebug(fmt.Sprintf("解析softmatch成功: 服务=%s, Pattern=%s",
+ softMatch.Service, softMatch.Pattern))
+ return softMatch, nil
+}
+
+// 解码模式字符串,处理转义序列
+func DecodePattern(s string) ([]byte, error) {
+ Common.LogDebug("开始解码pattern: " + s)
+ sByteOrigin := []byte(s)
+
+ // 处理十六进制、八进制和结构化转义序列
+ matchRe := regexp.MustCompile(`\\(x[0-9a-fA-F]{2}|[0-7]{1,3}|[aftnrv])`)
+ sByteDec := matchRe.ReplaceAllFunc(sByteOrigin, func(match []byte) (v []byte) {
+ var replace []byte
+
+ // 处理十六进制转义
+ if isHexCode(match) {
+ hexNum := match[2:]
+ byteNum, _ := strconv.ParseInt(string(hexNum), 16, 32)
+ if isReChar(byteNum) {
+ replace = []byte{'\\', uint8(byteNum)}
+ } else {
+ replace = []byte{uint8(byteNum)}
+ }
+ }
+
+ // 处理结构化转义字符
+ if isStructCode(match) {
+ structCodeMap := map[int][]byte{
+ 97: []byte{0x07}, // \a 响铃
+ 102: []byte{0x0c}, // \f 换页
+ 116: []byte{0x09}, // \t 制表符
+ 110: []byte{0x0a}, // \n 换行
+ 114: []byte{0x0d}, // \r 回车
+ 118: []byte{0x0b}, // \v 垂直制表符
+ }
+ replace = structCodeMap[int(match[1])]
+ }
+
+ // 处理八进制转义
+ if isOctalCode(match) {
+ octalNum := match[2:]
+ byteNum, _ := strconv.ParseInt(string(octalNum), 8, 32)
+ replace = []byte{uint8(byteNum)}
+ }
+ return replace
+ })
+
+ // 处理其他转义序列
+ matchRe2 := regexp.MustCompile(`\\([^\\])`)
+ sByteDec2 := matchRe2.ReplaceAllFunc(sByteDec, func(match []byte) (v []byte) {
+ if isOtherEscapeCode(match) {
+ return match
+ }
+ return match
+ })
+
+ Common.LogDebug("pattern解码完成")
+ return sByteDec2, nil
+}
+
+// ProbesRarity 用于按稀有度排序的探测器切片
+type ProbesRarity []Probe
+
+// Len 返回切片长度,实现 sort.Interface 接口
+func (ps ProbesRarity) Len() int {
+ return len(ps)
+}
+
+// Swap 交换切片中的两个元素,实现 sort.Interface 接口
+func (ps ProbesRarity) Swap(i, j int) {
+ ps[i], ps[j] = ps[j], ps[i]
+}
+
+// Less 比较函数,按稀有度升序排序,实现 sort.Interface 接口
+func (ps ProbesRarity) Less(i, j int) bool {
+ return ps[i].Rarity < ps[j].Rarity
+}
+
+// Target 定义目标结构体
+type Target struct {
+ IP string // 目标IP地址
+ Port int // 目标端口
+ Protocol string // 协议类型
+}
+
+// ContainsPort 检查指定端口是否在探测器的端口范围内
+func (p *Probe) ContainsPort(testPort int) bool {
+ Common.LogDebug(fmt.Sprintf("检查端口 %d 是否在探测器端口范围内: %s", testPort, p.Ports))
+
+ // 检查单个端口
+ ports := strings.Split(p.Ports, ",")
+ for _, port := range ports {
+ port = strings.TrimSpace(port)
+ cmpPort, err := strconv.Atoi(port)
+ if err == nil && testPort == cmpPort {
+ Common.LogDebug(fmt.Sprintf("端口 %d 匹配单个端口", testPort))
+ return true
+ }
+ }
+
+ // 检查端口范围
+ for _, port := range ports {
+ port = strings.TrimSpace(port)
+ if strings.Contains(port, "-") {
+ portRange := strings.Split(port, "-")
+ if len(portRange) != 2 {
+ Common.LogDebug("无效的端口范围格式: " + port)
+ continue
+ }
+
+ start, err1 := strconv.Atoi(strings.TrimSpace(portRange[0]))
+ end, err2 := strconv.Atoi(strings.TrimSpace(portRange[1]))
+
+ if err1 != nil || err2 != nil {
+ Common.LogDebug(fmt.Sprintf("解析端口范围失败: %s", port))
+ continue
+ }
+
+ if testPort >= start && testPort <= end {
+ Common.LogDebug(fmt.Sprintf("端口 %d 在范围 %d-%d 内", testPort, start, end))
+ return true
+ }
+ }
+ }
+
+ Common.LogDebug(fmt.Sprintf("端口 %d 不在探测器端口范围内", testPort))
+ return false
+}
+
+// MatchPattern 使用正则表达式匹配响应内容
+func (m *Match) MatchPattern(response []byte) bool {
+ // 将响应转换为字符串并进行匹配
+ responseStr := string([]rune(string(response)))
+ foundItems := m.PatternCompiled.FindStringSubmatch(responseStr)
+
+ if len(foundItems) > 0 {
+ m.FoundItems = foundItems
+ Common.LogDebug(fmt.Sprintf("匹配成功,找到 %d 个匹配项", len(foundItems)))
+ return true
+ }
+
+ return false
+}
+
+// ParseVersionInfo 解析版本信息并返回额外信息结构
+func (m *Match) ParseVersionInfo(response []byte) Extras {
+ Common.LogDebug("开始解析版本信息")
+ var extras = Extras{}
+
+ // 替换版本信息中的占位符
+ foundItems := m.FoundItems[1:] // 跳过第一个完整匹配项
+ versionInfo := m.VersionInfo
+ for index, value := range foundItems {
+ dollarName := "$" + strconv.Itoa(index+1)
+ versionInfo = strings.Replace(versionInfo, dollarName, value, -1)
+ }
+ Common.LogDebug("替换后的版本信息: " + versionInfo)
+
+ // 定义解析函数
+ parseField := func(field, pattern string) string {
+ patterns := []string{
+ pattern + `/([^/]*)/`, // 斜线分隔
+ pattern + `\|([^|]*)\|`, // 竖线分隔
+ }
+
+ for _, p := range patterns {
+ if strings.Contains(versionInfo, pattern) {
+ regex := regexp.MustCompile(p)
+ if matches := regex.FindStringSubmatch(versionInfo); len(matches) > 1 {
+ Common.LogDebug(fmt.Sprintf("解析到%s: %s", field, matches[1]))
+ return matches[1]
+ }
+ }
+ }
+ return ""
+ }
+
+ // 解析各个字段
+ extras.VendorProduct = parseField("厂商产品", " p")
+ extras.Version = parseField("版本", " v")
+ extras.Info = parseField("信息", " i")
+ extras.Hostname = parseField("主机名", " h")
+ extras.OperatingSystem = parseField("操作系统", " o")
+ extras.DeviceType = parseField("设备类型", " d")
+
+ // 特殊处理CPE
+ if strings.Contains(versionInfo, " cpe:/") || strings.Contains(versionInfo, " cpe:|") {
+ cpePatterns := []string{`cpe:/([^/]*)`, `cpe:\|([^|]*)`}
+ for _, pattern := range cpePatterns {
+ regex := regexp.MustCompile(pattern)
+ if cpeName := regex.FindStringSubmatch(versionInfo); len(cpeName) > 0 {
+ if len(cpeName) > 1 {
+ extras.CPE = cpeName[1]
+ } else {
+ extras.CPE = cpeName[0]
+ }
+ Common.LogDebug("解析到CPE: " + extras.CPE)
+ break
+ }
+ }
+ }
+
+ return extras
+}
+
+// ToMap 将 Extras 转换为 map[string]string
+func (e *Extras) ToMap() map[string]string {
+ Common.LogDebug("开始转换Extras为Map")
+ result := make(map[string]string)
+
+ // 定义字段映射
+ fields := map[string]string{
+ "vendor_product": e.VendorProduct,
+ "version": e.Version,
+ "info": e.Info,
+ "hostname": e.Hostname,
+ "os": e.OperatingSystem,
+ "device_type": e.DeviceType,
+ "cpe": e.CPE,
+ }
+
+ // 添加非空字段到结果map
+ for key, value := range fields {
+ if value != "" {
+ result[key] = value
+ Common.LogDebug(fmt.Sprintf("添加字段 %s: %s", key, value))
+ }
+ }
+
+ Common.LogDebug(fmt.Sprintf("转换完成,共有 %d 个字段", len(result)))
+ return result
+}
+
+func DecodeData(s string) ([]byte, error) {
+ if len(s) == 0 {
+ Common.LogDebug("输入数据为空")
+ return nil, fmt.Errorf("empty input")
+ }
+
+ Common.LogDebug(fmt.Sprintf("开始解码数据,长度: %d, 内容: %q", len(s), s))
+ sByteOrigin := []byte(s)
+
+ // 处理十六进制、八进制和结构化转义序列
+ matchRe := regexp.MustCompile(`\\(x[0-9a-fA-F]{2}|[0-7]{1,3}|[aftnrv])`)
+ sByteDec := matchRe.ReplaceAllFunc(sByteOrigin, func(match []byte) []byte {
+ // 处理十六进制转义
+ if isHexCode(match) {
+ hexNum := match[2:]
+ byteNum, err := strconv.ParseInt(string(hexNum), 16, 32)
+ if err != nil {
+ return match
+ }
+ return []byte{uint8(byteNum)}
+ }
+
+ // 处理结构化转义字符
+ if isStructCode(match) {
+ structCodeMap := map[int][]byte{
+ 97: []byte{0x07}, // \a 响铃
+ 102: []byte{0x0c}, // \f 换页
+ 116: []byte{0x09}, // \t 制表符
+ 110: []byte{0x0a}, // \n 换行
+ 114: []byte{0x0d}, // \r 回车
+ 118: []byte{0x0b}, // \v 垂直制表符
+ }
+ if replace, ok := structCodeMap[int(match[1])]; ok {
+ return replace
+ }
+ return match
+ }
+
+ // 处理八进制转义
+ if isOctalCode(match) {
+ octalNum := match[2:]
+ byteNum, err := strconv.ParseInt(string(octalNum), 8, 32)
+ if err != nil {
+ return match
+ }
+ return []byte{uint8(byteNum)}
+ }
+
+ Common.LogDebug(fmt.Sprintf("无法识别的转义序列: %s", string(match)))
+ return match
+ })
+
+ // 处理其他转义序列
+ matchRe2 := regexp.MustCompile(`\\([^\\])`)
+ sByteDec2 := matchRe2.ReplaceAllFunc(sByteDec, func(match []byte) []byte {
+ if len(match) < 2 {
+ return match
+ }
+ if isOtherEscapeCode(match) {
+ return []byte{match[1]}
+ }
+ return match
+ })
+
+ if len(sByteDec2) == 0 {
+ Common.LogDebug("解码后数据为空")
+ return nil, fmt.Errorf("decoded data is empty")
+ }
+
+ Common.LogDebug(fmt.Sprintf("解码完成,结果长度: %d, 内容: %x", len(sByteDec2), sByteDec2))
+ return sByteDec2, nil
+}
+
+// GetAddress 获取目标的完整地址(IP:端口)
+func (t *Target) GetAddress() string {
+ addr := t.IP + ":" + strconv.Itoa(t.Port)
+ Common.LogDebug("获取目标地址: " + addr)
+ return addr
+}
+
+// trimBanner 处理和清理横幅数据
+func trimBanner(buf []byte) string {
+ Common.LogDebug("开始处理横幅数据")
+ bufStr := string(buf)
+
+ // 特殊处理SMB协议
+ if strings.Contains(bufStr, "SMB") {
+ banner := hex.EncodeToString(buf)
+ if len(banner) > 0xa+6 && banner[0xa:0xa+6] == "534d42" { // "SMB" in hex
+ Common.LogDebug("检测到SMB协议数据")
+ plain := banner[0xa2:]
+ data, err := hex.DecodeString(plain)
+ if err != nil {
+ Common.LogDebug("SMB数据解码失败: " + err.Error())
+ return bufStr
+ }
+
+ // 解析domain
+ var domain string
+ var index int
+ for i, s := range data {
+ if s != 0 {
+ domain += string(s)
+ } else if i+1 < len(data) && data[i+1] == 0 {
+ index = i + 2
+ break
+ }
+ }
+
+ // 解析hostname
+ var hostname string
+ remainData := data[index:]
+ for i, h := range remainData {
+ if h != 0 {
+ hostname += string(h)
+ }
+ if i+1 < len(remainData) && remainData[i+1] == 0 {
+ break
+ }
+ }
+
+ smbBanner := fmt.Sprintf("hostname: %s domain: %s", hostname, domain)
+ Common.LogDebug("SMB横幅: " + smbBanner)
+ return smbBanner
+ }
+ }
+
+ // 处理常规数据
+ var src string
+ for _, ch := range bufStr {
+ if ch > 32 && ch < 125 {
+ src += string(ch)
+ } else {
+ src += " "
+ }
+ }
+
+ // 清理多余空白
+ re := regexp.MustCompile(`\s{2,}`)
+ src = re.ReplaceAllString(src, ".")
+ result := strings.TrimSpace(src)
+ Common.LogDebug("处理后的横幅: " + result)
+ return result
+}
+
+// Init 初始化VScan对象
+func (v *VScan) Init() {
+ Common.LogDebug("开始初始化VScan")
+ v.parseProbesFromContent(ProbeString)
+ v.parseProbesToMapKName()
+ v.SetusedProbes()
+ Common.LogDebug("VScan初始化完成")
+}
diff --git a/Core/PortInfo.go b/Core/PortInfo.go
new file mode 100644
index 0000000..bec4e86
--- /dev/null
+++ b/Core/PortInfo.go
@@ -0,0 +1,476 @@
+package Core
+
+import (
+ "fmt"
+ "github.com/shadow1ng/fscan/Common"
+ "io"
+ "net"
+ "strings"
+ "time"
+)
+
+// ServiceInfo 定义服务识别的结果信息
+type ServiceInfo struct {
+ Name string // 服务名称,如 http、ssh 等
+ Banner string // 服务返回的横幅信息
+ Version string // 服务版本号
+ Extras map[string]string // 其他额外信息,如操作系统、产品名等
+}
+
+// Result 定义单次探测的结果
+type Result struct {
+ Service Service // 识别出的服务信息
+ Banner string // 服务横幅
+ Extras map[string]string // 额外信息
+ Send []byte // 发送的探测数据
+ Recv []byte // 接收到的响应数据
+}
+
+// Service 定义服务的基本信息
+type Service struct {
+ Name string // 服务名称
+ Extras map[string]string // 服务的额外属性
+}
+
+// Info 定义单个端口探测的上下文信息
+type Info struct {
+ Address string // 目标IP地址
+ Port int // 目标端口
+ Conn net.Conn // 网络连接
+ Result Result // 探测结果
+ Found bool // 是否成功识别服务
+}
+
+// PortInfoScanner 定义端口服务识别器
+type PortInfoScanner struct {
+ Address string // 目标IP地址
+ Port int // 目标端口
+ Conn net.Conn // 网络连接
+ Timeout time.Duration // 超时时间
+ info *Info // 探测上下文
+}
+
+// 预定义的基础探测器
+var (
+ null = new(Probe) // 空探测器,用于基本协议识别
+ common = new(Probe) // 通用探测器,用于常见服务识别
+)
+
+// NewPortInfoScanner 创建新的端口服务识别器实例
+func NewPortInfoScanner(addr string, port int, conn net.Conn, timeout time.Duration) *PortInfoScanner {
+ return &PortInfoScanner{
+ Address: addr,
+ Port: port,
+ Conn: conn,
+ Timeout: timeout,
+ info: &Info{
+ Address: addr,
+ Port: port,
+ Conn: conn,
+ Result: Result{
+ Service: Service{},
+ },
+ },
+ }
+}
+
+// Identify 执行服务识别,返回识别结果
+func (s *PortInfoScanner) Identify() (*ServiceInfo, error) {
+ Common.LogDebug(fmt.Sprintf("开始识别服务 %s:%d", s.Address, s.Port))
+ s.info.PortInfo()
+
+ // 构造返回结果
+ serviceInfo := &ServiceInfo{
+ Name: s.info.Result.Service.Name,
+ Banner: s.info.Result.Banner,
+ Version: s.info.Result.Service.Extras["version"],
+ Extras: make(map[string]string),
+ }
+
+ // 复制额外信息
+ for k, v := range s.info.Result.Service.Extras {
+ serviceInfo.Extras[k] = v
+ }
+
+ Common.LogDebug(fmt.Sprintf("服务识别完成 %s:%d => %s", s.Address, s.Port, serviceInfo.Name))
+ return serviceInfo, nil
+}
+
+// PortInfo 执行端口服务识别的主要逻辑
+func (i *Info) PortInfo() {
+ // 1. 首先尝试读取服务的初始响应
+ if response, err := i.Read(); err == nil && len(response) > 0 {
+ Common.LogDebug(fmt.Sprintf("收到初始响应: %d 字节", len(response)))
+
+ // 使用基础探测器检查响应
+ Common.LogDebug("尝试使用基础探测器(null/common)检查响应")
+ if i.tryProbes(response, []*Probe{null, common}) {
+ Common.LogDebug("基础探测器匹配成功")
+ return
+ }
+ Common.LogDebug("基础探测器未匹配")
+ } else if err != nil {
+ Common.LogDebug(fmt.Sprintf("读取初始响应失败: %v", err))
+ }
+
+ // 记录已使用的探测器,避免重复使用
+ usedProbes := make(map[string]struct{})
+
+ // 2. 尝试使用端口专用探测器
+ Common.LogDebug(fmt.Sprintf("尝试使用端口 %d 的专用探测器", i.Port))
+ if i.processPortMapProbes(usedProbes) {
+ Common.LogDebug("端口专用探测器匹配成功")
+ return
+ }
+ Common.LogDebug("端口专用探测器未匹配")
+
+ // 3. 使用默认探测器列表
+ Common.LogDebug("尝试使用默认探测器列表")
+ if i.processDefaultProbes(usedProbes) {
+ Common.LogDebug("默认探测器匹配成功")
+ return
+ }
+ Common.LogDebug("默认探测器未匹配")
+
+ // 4. 如果所有探测都失败,标记为未知服务
+ if strings.TrimSpace(i.Result.Service.Name) == "" {
+ Common.LogDebug("未识别出服务,标记为 unknown")
+ i.Result.Service.Name = "unknown"
+ }
+}
+
+// tryProbes 尝试使用指定的探测器列表检查响应
+func (i *Info) tryProbes(response []byte, probes []*Probe) bool {
+ for _, probe := range probes {
+ Common.LogDebug(fmt.Sprintf("尝试探测器: %s", probe.Name))
+ i.GetInfo(response, probe)
+ if i.Found {
+ Common.LogDebug(fmt.Sprintf("探测器 %s 匹配成功", probe.Name))
+ return true
+ }
+ }
+ return false
+}
+
+// processPortMapProbes 处理端口映射中的专用探测器
+func (i *Info) processPortMapProbes(usedProbes map[string]struct{}) bool {
+ // 检查是否存在端口专用探测器
+ if len(Common.PortMap[i.Port]) == 0 {
+ Common.LogDebug(fmt.Sprintf("端口 %d 没有专用探测器", i.Port))
+ return false
+ }
+
+ // 遍历端口专用探测器
+ for _, name := range Common.PortMap[i.Port] {
+ Common.LogDebug(fmt.Sprintf("尝试端口专用探测器: %s", name))
+ usedProbes[name] = struct{}{}
+ probe := v.ProbesMapKName[name]
+
+ // 解码探测数据
+ probeData, err := DecodeData(probe.Data)
+ if err != nil || len(probeData) == 0 {
+ Common.LogDebug(fmt.Sprintf("探测器 %s 数据解码失败", name))
+ continue
+ }
+
+ // 发送探测数据并获取响应
+ Common.LogDebug(fmt.Sprintf("发送探测数据: %d 字节", len(probeData)))
+ if response := i.Connect(probeData); len(response) > 0 {
+ Common.LogDebug(fmt.Sprintf("收到响应: %d 字节", len(response)))
+
+ // 使用当前探测器检查响应
+ i.GetInfo(response, &probe)
+ if i.Found {
+ return true
+ }
+
+ // 根据探测器类型进行额外检查
+ switch name {
+ case "GenericLines":
+ if i.tryProbes(response, []*Probe{null}) {
+ return true
+ }
+ case "NULL":
+ continue
+ default:
+ if i.tryProbes(response, []*Probe{common}) {
+ return true
+ }
+ }
+ }
+ }
+ return false
+}
+
+// processDefaultProbes 处理默认探测器列表
+func (i *Info) processDefaultProbes(usedProbes map[string]struct{}) bool {
+ failCount := 0
+ const maxFailures = 10 // 最大失败次数
+
+ // 遍历默认探测器列表
+ for _, name := range Common.DefaultMap {
+ // 跳过已使用的探测器
+ if _, used := usedProbes[name]; used {
+ continue
+ }
+
+ probe := v.ProbesMapKName[name]
+ probeData, err := DecodeData(probe.Data)
+ if err != nil || len(probeData) == 0 {
+ continue
+ }
+
+ // 发送探测数据并获取响应
+ response := i.Connect(probeData)
+ if len(response) == 0 {
+ failCount++
+ if failCount > maxFailures {
+ return false
+ }
+ continue
+ }
+
+ // 使用当前探测器检查响应
+ i.GetInfo(response, &probe)
+ if i.Found {
+ return true
+ }
+
+ // 根据探测器类型进行额外检查
+ switch name {
+ case "GenericLines":
+ if i.tryProbes(response, []*Probe{null}) {
+ return true
+ }
+ case "NULL":
+ continue
+ default:
+ if i.tryProbes(response, []*Probe{common}) {
+ return true
+ }
+ }
+
+ // 尝试使用端口映射中的其他探测器
+ if len(Common.PortMap[i.Port]) > 0 {
+ for _, mappedName := range Common.PortMap[i.Port] {
+ usedProbes[mappedName] = struct{}{}
+ mappedProbe := v.ProbesMapKName[mappedName]
+ i.GetInfo(response, &mappedProbe)
+ if i.Found {
+ return true
+ }
+ }
+ }
+ }
+ return false
+}
+
+// GetInfo 分析响应数据并提取服务信息
+func (i *Info) GetInfo(response []byte, probe *Probe) {
+ Common.LogDebug(fmt.Sprintf("开始分析响应数据,长度: %d", len(response)))
+
+ // 响应数据有效性检查
+ if len(response) <= 0 {
+ Common.LogDebug("响应数据为空")
+ return
+ }
+
+ result := &i.Result
+ var (
+ softMatch Match
+ softFound bool
+ )
+
+ // 处理主要匹配规则
+ Common.LogDebug(fmt.Sprintf("处理探测器 %s 的主要匹配规则", probe.Name))
+ if matched, match := i.processMatches(response, probe.Matchs); matched {
+ Common.LogDebug("找到硬匹配")
+ return
+ } else if match != nil {
+ Common.LogDebug("找到软匹配")
+ softFound = true
+ softMatch = *match
+ }
+
+ // 处理回退匹配规则
+ if probe.Fallback != "" {
+ Common.LogDebug(fmt.Sprintf("尝试回退匹配: %s", probe.Fallback))
+ if fbProbe, ok := v.ProbesMapKName[probe.Fallback]; ok {
+ if matched, match := i.processMatches(response, fbProbe.Matchs); matched {
+ Common.LogDebug("回退匹配成功")
+ return
+ } else if match != nil {
+ Common.LogDebug("找到回退软匹配")
+ softFound = true
+ softMatch = *match
+ }
+ }
+ }
+
+ // 处理未找到匹配的情况
+ if !i.Found {
+ Common.LogDebug("未找到硬匹配,处理未匹配情况")
+ i.handleNoMatch(response, result, softFound, softMatch)
+ }
+}
+
+// processMatches 处理匹配规则集
+func (i *Info) processMatches(response []byte, matches *[]Match) (bool, *Match) {
+ Common.LogDebug(fmt.Sprintf("开始处理匹配规则,共 %d 条", len(*matches)))
+ var softMatch *Match
+
+ for _, match := range *matches {
+ if !match.MatchPattern(response) {
+ continue
+ }
+
+ if !match.IsSoft {
+ Common.LogDebug(fmt.Sprintf("找到硬匹配: %s", match.Service))
+ i.handleHardMatch(response, &match)
+ return true, nil
+ } else if softMatch == nil {
+ Common.LogDebug(fmt.Sprintf("找到软匹配: %s", match.Service))
+ tmpMatch := match
+ softMatch = &tmpMatch
+ }
+ }
+
+ return false, softMatch
+}
+
+// handleHardMatch 处理硬匹配结果
+func (i *Info) handleHardMatch(response []byte, match *Match) {
+ Common.LogDebug(fmt.Sprintf("处理硬匹配结果: %s", match.Service))
+ result := &i.Result
+ extras := match.ParseVersionInfo(response)
+ extrasMap := extras.ToMap()
+
+ result.Service.Name = match.Service
+ result.Extras = extrasMap
+ result.Banner = trimBanner(response)
+ result.Service.Extras = extrasMap
+
+ // 特殊处理 microsoft-ds 服务
+ if result.Service.Name == "microsoft-ds" {
+ Common.LogDebug("特殊处理 microsoft-ds 服务")
+ result.Service.Extras["hostname"] = result.Banner
+ }
+
+ i.Found = true
+ Common.LogDebug(fmt.Sprintf("服务识别结果: %s, Banner: %s", result.Service.Name, result.Banner))
+}
+
+// handleNoMatch 处理未找到匹配的情况
+func (i *Info) handleNoMatch(response []byte, result *Result, softFound bool, softMatch Match) {
+ Common.LogDebug("处理未匹配情况")
+ result.Banner = trimBanner(response)
+
+ if !softFound {
+ // 尝试识别 HTTP 服务
+ if strings.Contains(result.Banner, "HTTP/") ||
+ strings.Contains(result.Banner, "html") {
+ Common.LogDebug("识别为HTTP服务")
+ result.Service.Name = "http"
+ } else {
+ Common.LogDebug("未知服务")
+ result.Service.Name = "unknown"
+ }
+ } else {
+ Common.LogDebug("使用软匹配结果")
+ extras := softMatch.ParseVersionInfo(response)
+ result.Service.Extras = extras.ToMap()
+ result.Service.Name = softMatch.Service
+ i.Found = true
+ Common.LogDebug(fmt.Sprintf("软匹配服务: %s", result.Service.Name))
+ }
+}
+
+// Connect 发送数据并获取响应
+func (i *Info) Connect(msg []byte) []byte {
+ i.Write(msg)
+ reply, _ := i.Read()
+ return reply
+}
+
+const WrTimeout = 5 // 默认读写超时时间(秒)
+
+// Write 写入数据到连接
+func (i *Info) Write(msg []byte) error {
+ if i.Conn == nil {
+ return nil
+ }
+
+ // 设置写入超时
+ i.Conn.SetWriteDeadline(time.Now().Add(time.Second * time.Duration(WrTimeout)))
+
+ // 写入数据
+ _, err := i.Conn.Write(msg)
+ if err != nil && strings.Contains(err.Error(), "close") {
+ i.Conn.Close()
+ // 连接关闭时重试
+ i.Conn, err = net.DialTimeout("tcp4", fmt.Sprintf("%s:%d", i.Address, i.Port), time.Duration(6)*time.Second)
+ if err == nil {
+ i.Conn.SetWriteDeadline(time.Now().Add(time.Second * time.Duration(WrTimeout)))
+ _, err = i.Conn.Write(msg)
+ }
+ }
+
+ // 记录发送的数据
+ if err == nil {
+ i.Result.Send = msg
+ }
+
+ return err
+}
+
+// Read 从连接读取响应
+func (i *Info) Read() ([]byte, error) {
+ if i.Conn == nil {
+ return nil, nil
+ }
+
+ // 设置读取超时
+ i.Conn.SetReadDeadline(time.Now().Add(time.Second * time.Duration(WrTimeout)))
+
+ // 读取数据
+ result, err := readFromConn(i.Conn)
+ if err != nil && strings.Contains(err.Error(), "close") {
+ return result, err
+ }
+
+ // 记录接收到的数据
+ if len(result) > 0 {
+ i.Result.Recv = result
+ }
+
+ return result, err
+}
+
+// readFromConn 从连接读取数据的辅助函数
+func readFromConn(conn net.Conn) ([]byte, error) {
+ size := 2 * 1024 // 读取缓冲区大小
+ var result []byte
+
+ for {
+ buf := make([]byte, size)
+ count, err := conn.Read(buf)
+
+ if count > 0 {
+ result = append(result, buf[:count]...)
+ }
+
+ if err != nil {
+ if len(result) > 0 {
+ return result, nil
+ }
+ if err == io.EOF {
+ return result, nil
+ }
+ return result, err
+ }
+
+ if count < size {
+ return result, nil
+ }
+ }
+}
diff --git a/Core/PortScan.go b/Core/PortScan.go
new file mode 100644
index 0000000..eb048f3
--- /dev/null
+++ b/Core/PortScan.go
@@ -0,0 +1,262 @@
+package Core
+
+import (
+ "fmt"
+ "github.com/shadow1ng/fscan/Common"
+ "net"
+ "sort"
+ "strings"
+ "sync"
+ "time"
+)
+
+// Addr 表示待扫描的地址
+type Addr struct {
+ ip string // IP地址
+ port int // 端口号
+}
+
+// ScanResult 扫描结果
+type ScanResult struct {
+ Address string // IP地址
+ Port int // 端口号
+ Service *ServiceInfo // 服务信息
+}
+
+// PortScan 执行端口扫描
+// hostslist: 待扫描的主机列表
+// ports: 待扫描的端口范围
+// timeout: 超时时间(秒)
+// 返回活跃地址列表
+func PortScan(hostslist []string, ports string, timeout int64) []string {
+ var results []ScanResult
+ var aliveAddrs []string
+ var mu sync.Mutex
+
+ // 解析并验证端口列表
+ probePorts := Common.ParsePort(ports)
+ if len(probePorts) == 0 {
+ Common.LogError(fmt.Sprintf("端口格式错误: %s", ports))
+ return aliveAddrs
+ }
+
+ // 排除指定端口
+ probePorts = excludeNoPorts(probePorts)
+
+ // 初始化并发控制
+ workers := Common.ThreadNum
+ addrs := make(chan Addr, 100) // 待扫描地址通道
+ scanResults := make(chan ScanResult, 100) // 扫描结果通道
+ var wg sync.WaitGroup
+ var workerWg sync.WaitGroup
+
+ // 启动扫描工作协程
+ for i := 0; i < workers; i++ {
+ workerWg.Add(1)
+ go func() {
+ defer workerWg.Done()
+ for addr := range addrs {
+ PortConnect(addr, scanResults, timeout, &wg)
+ }
+ }()
+ }
+
+ // 启动结果处理协程
+ var resultWg sync.WaitGroup
+ resultWg.Add(1)
+ go func() {
+ defer resultWg.Done()
+ for result := range scanResults {
+ mu.Lock()
+ results = append(results, result)
+ aliveAddr := fmt.Sprintf("%s:%d", result.Address, result.Port)
+ aliveAddrs = append(aliveAddrs, aliveAddr)
+ mu.Unlock()
+ }
+ }()
+
+ // 分发扫描任务
+ for _, port := range probePorts {
+ for _, host := range hostslist {
+ wg.Add(1)
+ addrs <- Addr{host, port}
+ }
+ }
+
+ // 等待所有任务完成
+ close(addrs)
+ workerWg.Wait()
+ wg.Wait()
+ close(scanResults)
+ resultWg.Wait()
+
+ return aliveAddrs
+}
+
+// PortConnect 执行单个端口连接检测
+// addr: 待检测的地址
+// results: 结果通道
+// timeout: 超时时间
+// wg: 等待组
+func PortConnect(addr Addr, results chan<- ScanResult, timeout int64, wg *sync.WaitGroup) {
+ defer wg.Done()
+
+ var isOpen bool
+ var err error
+ var conn net.Conn
+
+ // 尝试建立TCP连接
+ conn, err = Common.WrapperTcpWithTimeout("tcp4",
+ fmt.Sprintf("%s:%v", addr.ip, addr.port),
+ time.Duration(timeout)*time.Second)
+ if err == nil {
+ defer conn.Close()
+ isOpen = true
+ }
+
+ if err != nil || !isOpen {
+ return
+ }
+
+ // 记录开放端口
+ address := fmt.Sprintf("%s:%d", addr.ip, addr.port)
+ Common.LogSuccess(fmt.Sprintf("端口开放 %s", address))
+
+ // 保存端口扫描结果
+ portResult := &Common.ScanResult{
+ Time: time.Now(),
+ Type: Common.PORT,
+ Target: addr.ip,
+ Status: "open",
+ Details: map[string]interface{}{
+ "port": addr.port,
+ },
+ }
+ Common.SaveResult(portResult)
+
+ // 构造扫描结果
+ result := ScanResult{
+ Address: addr.ip,
+ Port: addr.port,
+ }
+
+ // 执行服务识别
+ if !Common.SkipFingerprint && conn != nil {
+ scanner := NewPortInfoScanner(addr.ip, addr.port, conn, time.Duration(timeout)*time.Second)
+ if serviceInfo, err := scanner.Identify(); err == nil {
+ result.Service = serviceInfo
+
+ // 构造服务识别日志
+ var logMsg strings.Builder
+ logMsg.WriteString(fmt.Sprintf("服务识别 %s => ", address))
+
+ if serviceInfo.Name != "unknown" {
+ logMsg.WriteString(fmt.Sprintf("[%s]", serviceInfo.Name))
+ }
+
+ if serviceInfo.Version != "" {
+ logMsg.WriteString(fmt.Sprintf(" 版本:%s", serviceInfo.Version))
+ }
+
+ // 收集服务详细信息
+ details := map[string]interface{}{
+ "port": addr.port,
+ "service": serviceInfo.Name,
+ }
+
+ // 添加版本信息
+ if serviceInfo.Version != "" {
+ details["version"] = serviceInfo.Version
+ }
+
+ // 添加产品信息
+ if v, ok := serviceInfo.Extras["vendor_product"]; ok && v != "" {
+ details["product"] = v
+ logMsg.WriteString(fmt.Sprintf(" 产品:%s", v))
+ }
+
+ // 添加操作系统信息
+ if v, ok := serviceInfo.Extras["os"]; ok && v != "" {
+ details["os"] = v
+ logMsg.WriteString(fmt.Sprintf(" 系统:%s", v))
+ }
+
+ // 添加额外信息
+ if v, ok := serviceInfo.Extras["info"]; ok && v != "" {
+ details["info"] = v
+ logMsg.WriteString(fmt.Sprintf(" 信息:%s", v))
+ }
+
+ // 添加Banner信息
+ if len(serviceInfo.Banner) > 0 && len(serviceInfo.Banner) < 100 {
+ details["banner"] = strings.TrimSpace(serviceInfo.Banner)
+ logMsg.WriteString(fmt.Sprintf(" Banner:[%s]", strings.TrimSpace(serviceInfo.Banner)))
+ }
+
+ // 保存服务识别结果
+ serviceResult := &Common.ScanResult{
+ Time: time.Now(),
+ Type: Common.SERVICE,
+ Target: addr.ip,
+ Status: "identified",
+ Details: details,
+ }
+ Common.SaveResult(serviceResult)
+
+ Common.LogSuccess(logMsg.String())
+ }
+ }
+
+ results <- result
+}
+
+// NoPortScan 生成端口列表(不进行扫描)
+// hostslist: 主机列表
+// ports: 端口范围
+// 返回地址列表
+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 排除指定的端口
+// ports: 原始端口列表
+// 返回过滤后的端口列表
+func excludeNoPorts(ports []int) []int {
+ noPorts := Common.ParsePort(Common.ExcludePorts)
+ 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 0000000..44fc693
--- /dev/null
+++ b/Core/Registry.go
@@ -0,0 +1,254 @@
+package Core
+
+import (
+ "github.com/shadow1ng/fscan/Common"
+ "github.com/shadow1ng/fscan/Plugins"
+)
+
+// init 初始化并注册所有扫描插件
+// 包括标准端口服务扫描、特殊扫描类型和本地信息收集等
+func init() {
+ // 1. 标准网络服务扫描插件
+ // 文件传输和远程访问服务
+ Common.RegisterPlugin("ftp", Common.ScanPlugin{
+ Name: "FTP",
+ Ports: []int{21},
+ ScanFunc: Plugins.FtpScan,
+ })
+
+ Common.RegisterPlugin("ssh", Common.ScanPlugin{
+ Name: "SSH",
+ Ports: []int{22, 2222},
+ ScanFunc: Plugins.SshScan,
+ })
+
+ Common.RegisterPlugin("telnet", Common.ScanPlugin{
+ Name: "Telnet",
+ Ports: []int{23},
+ ScanFunc: Plugins.TelnetScan,
+ })
+
+ // Windows网络服务
+ Common.RegisterPlugin("findnet", Common.ScanPlugin{
+ Name: "FindNet",
+ Ports: []int{135},
+ ScanFunc: Plugins.Findnet,
+ })
+
+ Common.RegisterPlugin("netbios", Common.ScanPlugin{
+ Name: "NetBIOS",
+ Ports: []int{139},
+ ScanFunc: Plugins.NetBIOS,
+ })
+
+ Common.RegisterPlugin("smb", Common.ScanPlugin{
+ Name: "SMB",
+ Ports: []int{445},
+ ScanFunc: Plugins.SmbScan,
+ })
+
+ // 数据库服务
+ Common.RegisterPlugin("mssql", Common.ScanPlugin{
+ Name: "MSSQL",
+ Ports: []int{1433, 1434},
+ ScanFunc: Plugins.MssqlScan,
+ })
+
+ Common.RegisterPlugin("oracle", Common.ScanPlugin{
+ Name: "Oracle",
+ Ports: []int{1521, 1522, 1526},
+ ScanFunc: Plugins.OracleScan,
+ })
+
+ Common.RegisterPlugin("mysql", Common.ScanPlugin{
+ Name: "MySQL",
+ Ports: []int{3306, 3307, 13306, 33306},
+ ScanFunc: Plugins.MysqlScan,
+ })
+
+ // 中间件和消息队列服务
+ Common.RegisterPlugin("elasticsearch", Common.ScanPlugin{
+ Name: "Elasticsearch",
+ Ports: []int{9200, 9300},
+ ScanFunc: Plugins.ElasticScan,
+ })
+
+ Common.RegisterPlugin("rabbitmq", Common.ScanPlugin{
+ Name: "RabbitMQ",
+ Ports: []int{5672, 5671, 15672, 15671},
+ ScanFunc: Plugins.RabbitMQScan,
+ })
+
+ Common.RegisterPlugin("kafka", Common.ScanPlugin{
+ Name: "Kafka",
+ Ports: []int{9092, 9093},
+ ScanFunc: Plugins.KafkaScan,
+ })
+
+ Common.RegisterPlugin("activemq", Common.ScanPlugin{
+ Name: "ActiveMQ",
+ Ports: []int{61613},
+ ScanFunc: Plugins.ActiveMQScan,
+ })
+
+ // 目录和认证服务
+ Common.RegisterPlugin("ldap", Common.ScanPlugin{
+ Name: "LDAP",
+ Ports: []int{389, 636},
+ ScanFunc: Plugins.LDAPScan,
+ })
+
+ // 邮件服务
+ Common.RegisterPlugin("smtp", Common.ScanPlugin{
+ Name: "SMTP",
+ Ports: []int{25, 465, 587},
+ ScanFunc: Plugins.SmtpScan,
+ })
+
+ Common.RegisterPlugin("imap", Common.ScanPlugin{
+ Name: "IMAP",
+ Ports: []int{143, 993},
+ ScanFunc: Plugins.IMAPScan,
+ })
+
+ Common.RegisterPlugin("pop3", Common.ScanPlugin{
+ Name: "POP3",
+ Ports: []int{110, 995},
+ ScanFunc: Plugins.POP3Scan,
+ })
+
+ // 网络管理和监控服务
+ Common.RegisterPlugin("snmp", Common.ScanPlugin{
+ Name: "SNMP",
+ Ports: []int{161, 162},
+ ScanFunc: Plugins.SNMPScan,
+ })
+
+ Common.RegisterPlugin("modbus", Common.ScanPlugin{
+ Name: "Modbus",
+ Ports: []int{502, 5020},
+ ScanFunc: Plugins.ModbusScan,
+ })
+
+ // 数据同步和备份服务
+ Common.RegisterPlugin("rsync", Common.ScanPlugin{
+ Name: "Rsync",
+ Ports: []int{873},
+ ScanFunc: Plugins.RsyncScan,
+ })
+
+ // NoSQL数据库
+ Common.RegisterPlugin("cassandra", Common.ScanPlugin{
+ Name: "Cassandra",
+ Ports: []int{9042},
+ ScanFunc: Plugins.CassandraScan,
+ })
+
+ Common.RegisterPlugin("neo4j", Common.ScanPlugin{
+ Name: "Neo4j",
+ Ports: []int{7687},
+ ScanFunc: Plugins.Neo4jScan,
+ })
+
+ // 远程桌面和显示服务
+ Common.RegisterPlugin("rdp", Common.ScanPlugin{
+ Name: "RDP",
+ Ports: []int{3389, 13389, 33389},
+ ScanFunc: Plugins.RdpScan,
+ })
+
+ Common.RegisterPlugin("postgres", Common.ScanPlugin{
+ Name: "PostgreSQL",
+ Ports: []int{5432, 5433},
+ ScanFunc: Plugins.PostgresScan,
+ })
+
+ Common.RegisterPlugin("vnc", Common.ScanPlugin{
+ Name: "VNC",
+ Ports: []int{5900, 5901, 5902},
+ ScanFunc: Plugins.VncScan,
+ })
+
+ // 缓存和键值存储服务
+ Common.RegisterPlugin("redis", Common.ScanPlugin{
+ Name: "Redis",
+ Ports: []int{6379, 6380, 16379},
+ ScanFunc: Plugins.RedisScan,
+ })
+
+ Common.RegisterPlugin("fcgi", Common.ScanPlugin{
+ Name: "FastCGI",
+ Ports: []int{9000},
+ ScanFunc: Plugins.FcgiScan,
+ })
+
+ Common.RegisterPlugin("memcached", Common.ScanPlugin{
+ Name: "Memcached",
+ Ports: []int{11211},
+ ScanFunc: Plugins.MemcachedScan,
+ })
+
+ Common.RegisterPlugin("mongodb", Common.ScanPlugin{
+ Name: "MongoDB",
+ Ports: []int{27017, 27018},
+ ScanFunc: Plugins.MongodbScan,
+ })
+
+ // 2. 特殊漏洞扫描插件
+ Common.RegisterPlugin("ms17010", Common.ScanPlugin{
+ Name: "MS17010",
+ Ports: []int{445},
+ ScanFunc: Plugins.MS17010,
+ })
+
+ Common.RegisterPlugin("smbghost", Common.ScanPlugin{
+ Name: "SMBGhost",
+ Ports: []int{445},
+ ScanFunc: Plugins.SmbGhost,
+ })
+
+ // 3. Web应用扫描插件
+ Common.RegisterPlugin("webtitle", Common.ScanPlugin{
+ Name: "WebTitle",
+ Ports: Common.ParsePortsFromString(Common.WebPorts),
+ ScanFunc: Plugins.WebTitle,
+ })
+
+ Common.RegisterPlugin("webpoc", Common.ScanPlugin{
+ Name: "WebPoc",
+ Ports: Common.ParsePortsFromString(Common.WebPorts),
+ ScanFunc: Plugins.WebPoc,
+ })
+
+ // 4. Windows系统专用插件
+ Common.RegisterPlugin("smb2", Common.ScanPlugin{
+ Name: "SMBScan2",
+ Ports: []int{445},
+ ScanFunc: Plugins.SmbScan2,
+ })
+
+ Common.RegisterPlugin("wmiexec", Common.ScanPlugin{
+ Name: "WMIExec",
+ Ports: []int{135},
+ ScanFunc: Plugins.WmiExec,
+ })
+
+ // 5. 本地信息收集插件
+ Common.RegisterPlugin("localinfo", Common.ScanPlugin{
+ Name: "LocalInfo",
+ Ports: []int{},
+ ScanFunc: Plugins.LocalInfoScan,
+ })
+
+ Common.RegisterPlugin("dcinfo", Common.ScanPlugin{
+ Name: "DCInfo",
+ Ports: []int{},
+ ScanFunc: Plugins.DCInfoScan,
+ })
+
+ Common.RegisterPlugin("minidump", Common.ScanPlugin{
+ Name: "MiniDump",
+ Ports: []int{},
+ ScanFunc: Plugins.MiniDump,
+ })
+}
diff --git a/Core/Scanner.go b/Core/Scanner.go
new file mode 100644
index 0000000..a3ab49d
--- /dev/null
+++ b/Core/Scanner.go
@@ -0,0 +1,467 @@
+package Core
+
+import (
+ "fmt"
+ "github.com/schollz/progressbar/v3"
+ "github.com/shadow1ng/fscan/Common"
+ "github.com/shadow1ng/fscan/WebScan/lib"
+ "sort"
+ "strconv"
+ "strings"
+ "sync"
+ "sync/atomic"
+ "time"
+)
+
+// 全局变量定义
+var (
+ LocalScan bool // 本地扫描模式标识
+ WebScan bool // Web扫描模式标识
+)
+
+// Scan 执行扫描主流程
+// info: 主机信息结构体,包含扫描目标的基本信息
+func Scan(info Common.HostInfo) {
+ Common.LogInfo("开始信息扫描")
+
+ // 初始化HTTP客户端配置
+ lib.Inithttp()
+
+ // 初始化并发控制
+ ch := make(chan struct{}, Common.ThreadNum)
+ wg := sync.WaitGroup{}
+
+ // 根据扫描模式执行不同的扫描策略
+ switch {
+ case Common.LocalMode:
+ // 本地信息收集模式
+ LocalScan = true
+ executeLocalScan(info, &ch, &wg)
+ case len(Common.URLs) > 0:
+ // Web扫描模式
+ WebScan = true
+ executeWebScan(info, &ch, &wg)
+ default:
+ // 主机扫描模式
+ executeHostScan(info, &ch, &wg)
+ }
+
+ // 等待所有扫描任务完成
+ finishScan(&wg)
+}
+
+// executeLocalScan 执行本地扫描
+// info: 主机信息
+// ch: 并发控制通道
+// wg: 等待组
+func executeLocalScan(info Common.HostInfo, ch *chan struct{}, wg *sync.WaitGroup) {
+ Common.LogInfo("执行本地信息收集")
+
+ // 获取本地模式支持的插件列表
+ validLocalPlugins := getValidPlugins(Common.ModeLocal)
+
+ // 验证扫描模式的合法性
+ if err := validateScanMode(validLocalPlugins, Common.ModeLocal); err != nil {
+ Common.LogError(err.Error())
+ return
+ }
+
+ // 输出使用的插件信息
+ if Common.ScanMode == Common.ModeLocal {
+ Common.LogInfo("使用全部本地插件")
+ Common.ParseScanMode(Common.ScanMode)
+ } else {
+ Common.LogInfo(fmt.Sprintf("使用插件: %s", Common.ScanMode))
+ }
+
+ // 执行扫描任务
+ executeScans([]Common.HostInfo{info}, ch, wg)
+}
+
+// executeWebScan 执行Web扫描
+// info: 主机信息
+// ch: 并发控制通道
+// wg: 等待组
+func executeWebScan(info Common.HostInfo, ch *chan struct{}, wg *sync.WaitGroup) {
+ Common.LogInfo("开始Web扫描")
+
+ // 获取Web模式支持的插件列表
+ validWebPlugins := getValidPlugins(Common.ModeWeb)
+
+ // 验证扫描模式的合法性
+ if err := validateScanMode(validWebPlugins, Common.ModeWeb); err != nil {
+ Common.LogError(err.Error())
+ return
+ }
+
+ // 处理目标URL列表
+ var targetInfos []Common.HostInfo
+ for _, url := range Common.URLs {
+ urlInfo := info
+ // 确保URL包含协议头
+ if !strings.HasPrefix(url, "http://") && !strings.HasPrefix(url, "https://") {
+ url = "http://" + url
+ }
+ urlInfo.Url = url
+ targetInfos = append(targetInfos, urlInfo)
+ }
+
+ // 输出使用的插件信息
+ if Common.ScanMode == Common.ModeWeb {
+ Common.LogInfo("使用全部Web插件")
+ Common.ParseScanMode(Common.ScanMode)
+ } else {
+ Common.LogInfo(fmt.Sprintf("使用插件: %s", Common.ScanMode))
+ }
+
+ // 执行扫描任务
+ executeScans(targetInfos, ch, wg)
+}
+
+// executeHostScan 执行主机扫描
+// info: 主机信息
+// ch: 并发控制通道
+// wg: 等待组
+func executeHostScan(info Common.HostInfo, ch *chan struct{}, wg *sync.WaitGroup) {
+ // 验证扫描目标
+ if info.Host == "" {
+ Common.LogError("未指定扫描目标")
+ return
+ }
+
+ // 解析目标主机
+ hosts, err := Common.ParseIP(info.Host, Common.HostsFile, Common.ExcludeHosts)
+ if err != nil {
+ Common.LogError(fmt.Sprintf("解析主机错误: %v", err))
+ return
+ }
+
+ Common.LogInfo("开始主机扫描")
+ executeScan(hosts, info, ch, wg)
+}
+
+// getValidPlugins 获取指定模式下的有效插件列表
+// mode: 扫描模式
+// 返回: 有效插件映射表
+func getValidPlugins(mode string) map[string]bool {
+ validPlugins := make(map[string]bool)
+ for _, plugin := range Common.PluginGroups[mode] {
+ validPlugins[plugin] = true
+ }
+ return validPlugins
+}
+
+// validateScanMode 验证扫描模式的合法性
+// validPlugins: 有效插件列表
+// mode: 扫描模式
+// 返回: 错误信息
+func validateScanMode(validPlugins map[string]bool, mode string) error {
+ if Common.ScanMode == "" || Common.ScanMode == "All" {
+ Common.ScanMode = mode
+ } else if _, exists := validPlugins[Common.ScanMode]; !exists {
+ return fmt.Errorf("无效的%s插件: %s", mode, Common.ScanMode)
+ }
+ return nil
+}
+
+// executeScan 执行主扫描流程
+// hosts: 目标主机列表
+// info: 主机信息
+// ch: 并发控制通道
+// wg: 等待组
+func executeScan(hosts []string, info Common.HostInfo, ch *chan struct{}, wg *sync.WaitGroup) {
+ var targetInfos []Common.HostInfo
+
+ // 处理主机和端口扫描
+ if len(hosts) > 0 || len(Common.HostPort) > 0 {
+ // 检查主机存活性
+ if shouldPingScan(hosts) {
+ hosts = CheckLive(hosts, Common.UsePing)
+ Common.LogInfo(fmt.Sprintf("存活主机数量: %d", len(hosts)))
+ if Common.IsICMPScan() {
+ return
+ }
+ }
+
+ // 获取存活端口
+ alivePorts := getAlivePorts(hosts)
+ if len(alivePorts) > 0 {
+ targetInfos = prepareTargetInfos(alivePorts, info)
+ }
+ }
+
+ // 添加URL扫描目标
+ targetInfos = appendURLTargets(targetInfos, info)
+
+ // 执行漏洞扫描
+ if len(targetInfos) > 0 {
+ Common.LogInfo("开始漏洞扫描")
+ executeScans(targetInfos, ch, wg)
+ }
+}
+
+// shouldPingScan 判断是否需要执行ping扫描
+// hosts: 目标主机列表
+// 返回: 是否需要ping扫描
+func shouldPingScan(hosts []string) bool {
+ return (Common.DisablePing == false && len(hosts) > 1) || Common.IsICMPScan()
+}
+
+// getAlivePorts 获取存活端口列表
+// hosts: 目标主机列表
+// 返回: 存活端口列表
+func getAlivePorts(hosts []string) []string {
+ var alivePorts []string
+
+ // 根据扫描模式选择端口扫描方式
+ if Common.IsWebScan() {
+ alivePorts = NoPortScan(hosts, Common.Ports)
+ } else if len(hosts) > 0 {
+ alivePorts = PortScan(hosts, Common.Ports, Common.Timeout)
+ Common.LogInfo(fmt.Sprintf("存活端口数量: %d", len(alivePorts)))
+ if Common.IsPortScan() {
+ return nil
+ }
+ }
+
+ // 合并额外指定的端口
+ if len(Common.HostPort) > 0 {
+ alivePorts = append(alivePorts, Common.HostPort...)
+ alivePorts = Common.RemoveDuplicate(alivePorts)
+ Common.HostPort = nil
+ Common.LogInfo(fmt.Sprintf("存活端口数量: %d", len(alivePorts)))
+ }
+
+ return alivePorts
+}
+
+// appendURLTargets 添加URL扫描目标
+// targetInfos: 现有目标列表
+// baseInfo: 基础主机信息
+// 返回: 更新后的目标列表
+func appendURLTargets(targetInfos []Common.HostInfo, baseInfo Common.HostInfo) []Common.HostInfo {
+ for _, url := range Common.URLs {
+ urlInfo := baseInfo
+ urlInfo.Url = url
+ targetInfos = append(targetInfos, urlInfo)
+ }
+ return targetInfos
+}
+
+// prepareTargetInfos 准备扫描目标信息
+// alivePorts: 存活端口列表
+// baseInfo: 基础主机信息
+// 返回: 目标信息列表
+func prepareTargetInfos(alivePorts []string, baseInfo Common.HostInfo) []Common.HostInfo {
+ var infos []Common.HostInfo
+ for _, targetIP := range alivePorts {
+ hostParts := strings.Split(targetIP, ":")
+ if len(hostParts) != 2 {
+ Common.LogError(fmt.Sprintf("无效的目标地址格式: %s", targetIP))
+ continue
+ }
+ info := baseInfo
+ info.Host = hostParts[0]
+ info.Ports = hostParts[1]
+ infos = append(infos, info)
+ }
+ return infos
+}
+
+// ScanTask 扫描任务结构体
+type ScanTask struct {
+ pluginName string // 插件名称
+ target Common.HostInfo // 目标信息
+}
+
+// executeScans 执行扫描任务
+// targets: 目标列表
+// ch: 并发控制通道
+// wg: 等待组
+func executeScans(targets []Common.HostInfo, ch *chan struct{}, wg *sync.WaitGroup) {
+ mode := Common.GetScanMode()
+
+ // 获取要执行的插件列表
+ pluginsToRun, isSinglePlugin := getPluginsToRun(mode)
+
+ var tasks []ScanTask
+ actualTasks := 0
+ loadedPlugins := make([]string, 0)
+
+ // 收集扫描任务
+ for _, target := range targets {
+ targetPort, _ := strconv.Atoi(target.Ports)
+ for _, pluginName := range pluginsToRun {
+ plugin, exists := Common.PluginManager[pluginName]
+ if !exists {
+ continue
+ }
+ taskAdded, newTasks := collectScanTasks(plugin, target, targetPort, pluginName, isSinglePlugin)
+ if taskAdded {
+ actualTasks += len(newTasks)
+ loadedPlugins = append(loadedPlugins, pluginName)
+ tasks = append(tasks, newTasks...)
+ }
+ }
+ }
+
+ // 处理插件列表
+ finalPlugins := getUniquePlugins(loadedPlugins)
+ Common.LogInfo(fmt.Sprintf("加载的插件: %s", strings.Join(finalPlugins, ", ")))
+
+ // 初始化进度条
+ initializeProgressBar(actualTasks)
+
+ // 执行扫描任务
+ for _, task := range tasks {
+ AddScan(task.pluginName, task.target, ch, wg)
+ }
+}
+
+// getPluginsToRun 获取要执行的插件列表
+// mode: 扫描模式
+// 返回: 插件列表和是否为单插件模式
+func getPluginsToRun(mode string) ([]string, bool) {
+ var pluginsToRun []string
+ isSinglePlugin := false
+
+ if plugins := Common.GetPluginsForMode(mode); plugins != nil {
+ pluginsToRun = plugins
+ } else {
+ pluginsToRun = []string{mode}
+ isSinglePlugin = true
+ }
+
+ return pluginsToRun, isSinglePlugin
+}
+
+// collectScanTasks 收集扫描任务
+// plugin: 插件信息
+// target: 目标信息
+// targetPort: 目标端口
+// pluginName: 插件名称
+// isSinglePlugin: 是否为单插件模式
+// 返回: 是否添加任务和任务列表
+func collectScanTasks(plugin Common.ScanPlugin, target Common.HostInfo, targetPort int, pluginName string, isSinglePlugin bool) (bool, []ScanTask) {
+ var tasks []ScanTask
+ taskAdded := false
+
+ if WebScan || LocalScan || isSinglePlugin || len(plugin.Ports) == 0 || plugin.HasPort(targetPort) {
+ taskAdded = true
+ tasks = append(tasks, ScanTask{
+ pluginName: pluginName,
+ target: target,
+ })
+ }
+
+ return taskAdded, tasks
+}
+
+// getUniquePlugins 获取去重后的插件列表
+// loadedPlugins: 已加载的插件列表
+// 返回: 去重并排序后的插件列表
+func getUniquePlugins(loadedPlugins []string) []string {
+ uniquePlugins := make(map[string]struct{})
+ for _, p := range loadedPlugins {
+ uniquePlugins[p] = struct{}{}
+ }
+
+ finalPlugins := make([]string, 0, len(uniquePlugins))
+ for p := range uniquePlugins {
+ finalPlugins = append(finalPlugins, p)
+ }
+
+ sort.Strings(finalPlugins)
+ return finalPlugins
+}
+
+// initializeProgressBar 初始化进度条
+// actualTasks: 实际任务数量
+func initializeProgressBar(actualTasks int) {
+ if Common.ShowProgress {
+ Common.ProgressBar = progressbar.NewOptions(actualTasks,
+ progressbar.OptionEnableColorCodes(true),
+ progressbar.OptionShowCount(),
+ progressbar.OptionSetWidth(15),
+ progressbar.OptionSetDescription("[cyan]扫描进度:[reset]"),
+ progressbar.OptionSetTheme(progressbar.Theme{
+ Saucer: "[green]=[reset]",
+ SaucerHead: "[green]>[reset]",
+ SaucerPadding: " ",
+ BarStart: "[",
+ BarEnd: "]",
+ }),
+ progressbar.OptionThrottle(65*time.Millisecond),
+ progressbar.OptionUseANSICodes(true),
+ progressbar.OptionSetRenderBlankState(true),
+ )
+ }
+}
+
+// finishScan 完成扫描任务
+// wg: 等待组
+func finishScan(wg *sync.WaitGroup) {
+ wg.Wait()
+ if Common.ProgressBar != nil {
+ Common.ProgressBar.Finish()
+ fmt.Println()
+ }
+ Common.LogSuccess(fmt.Sprintf("扫描已完成: %v/%v", Common.End, Common.Num))
+}
+
+// Mutex 用于保护共享资源的并发访问
+var Mutex = &sync.Mutex{}
+
+// AddScan 添加扫描任务并启动扫描
+// plugin: 插件名称
+// info: 目标信息
+// ch: 并发控制通道
+// wg: 等待组
+func AddScan(plugin string, info Common.HostInfo, ch *chan struct{}, wg *sync.WaitGroup) {
+ *ch <- struct{}{}
+ wg.Add(1)
+
+ go func() {
+ defer func() {
+ wg.Done()
+ <-*ch
+ }()
+
+ atomic.AddInt64(&Common.Num, 1)
+ ScanFunc(&plugin, &info)
+ updateScanProgress(&info)
+ }()
+}
+
+// ScanFunc 执行扫描插件
+// name: 插件名称
+// info: 目标信息
+func ScanFunc(name *string, info *Common.HostInfo) {
+ defer func() {
+ if err := recover(); err != nil {
+ Common.LogError(fmt.Sprintf("扫描错误 %v:%v - %v", info.Host, info.Ports, err))
+ }
+ }()
+
+ plugin, exists := Common.PluginManager[*name]
+ if !exists {
+ Common.LogInfo(fmt.Sprintf("扫描类型 %v 无对应插件,已跳过", *name))
+ return
+ }
+
+ if err := plugin.ScanFunc(info); err != nil {
+ Common.LogError(fmt.Sprintf("扫描错误 %v:%v - %v", info.Host, info.Ports, err))
+ }
+}
+
+// updateScanProgress 更新扫描进度
+// info: 目标信息
+func updateScanProgress(info *Common.HostInfo) {
+ Common.OutputMutex.Lock()
+ atomic.AddInt64(&Common.End, 1)
+ if Common.ProgressBar != nil {
+ fmt.Print("\033[2K\r")
+ Common.ProgressBar.Add(1)
+ }
+ Common.OutputMutex.Unlock()
+}
diff --git a/Core/nmap-service-probes.txt b/Core/nmap-service-probes.txt
new file mode 100644
index 0000000..a503108
--- /dev/null
+++ b/Core/nmap-service-probes.txt
@@ -0,0 +1,16624 @@
+# Nmap service detection probe list -*- mode: fundamental; -*-
+# $Id$
+#
+# This is a database of custom probes and expected responses that the
+# Nmap Security Scanner ( https://nmap.org ) uses to
+# identify what services (eg http, smtp, dns, etc.) are listening on
+# open ports. Contributions to this database are welcome.
+# Instructions for obtaining and submitting service detection fingerprints can
+# be found in the Nmap Network Scanning book and online at
+# https://nmap.org/book/vscan-community.html
+#
+# This collection of probe data is (C) 1998-2020 by Insecure.Com
+# LLC. It is distributed under the Nmap Public Source license as
+# provided in the LICENSE file of the source distribution or at
+# https://nmap.org/data/LICENSE . Note that this license
+# requires you to license your own work under a compatible open source
+# license. If you wish to embed Nmap technology into proprietary
+# software, we sell alternative licenses (contact sales@insecure.com).
+# Dozens of software vendors already license Nmap technology such as
+# host discovery, port scanning, OS detection, and version detection.
+# For more details, see https://nmap.org/book/man-legal.html
+#
+# For details on how Nmap version detection works, why it was added,
+# the grammar of this file, and how to detect and contribute new
+# services, see https://nmap.org/book/vscan.html.
+
+# The Exclude directive takes a comma separated list of ports.
+# The format is exactly the same as the -p switch.
+Exclude T:9100-9107
+
+# This is the NULL probe that just compares any banners given to us
+##############################NEXT PROBE##############################
+Probe TCP NULL q||
+# Wait for at least 6 seconds for data. It used to be 5, but some
+# smtp services have lately been instituting an artificial pause (see
+# FEATURE('greet_pause') in Sendmail, for example)
+totalwaitms 6000
+# If the service closes the connection before 3 seconds, it's probably
+# tcpwrapped. Adjust up or down depending on your false-positive rate.
+tcpwrappedms 3000
+
+match 1c-server m|^S\xf5\xc6\x1a{| p/1C:Enterprise business management server/
+
+match 3cx-tunnel m|^\x04\0\xfb\xffLAPK| p/3CX Tunnel Protocol/
+
+match 4d-server m|^\0\0\0H\0\0\0\x02.[^\0]*\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0$|s p/4th Dimension database server/ cpe:/a:4d_sas:4d/
+
+match aastra-pbx m|^BUSY$| p|Aastra/Mitel 400-series PBX service port|
+match acap m|^\* ACAP \(IMPLEMENTATION \"CommuniGate Pro ACAP (\d[-.\w]+)\"\) | p/CommuniGate Pro ACAP server/ v/$1/ i/for mail client preference sharing/ cpe:/a:stalker:communigate_pro:$1/
+match acarsd m|^g\0\0\0\x1b\0\0\0\0\0\0\0acarsd\t([\w._-]+)\tAPI-([\w._-]+)\)\0\0\0\x06\x05\0\0\0\0\0\0<\?xml | p/acarsd/ v/$1/ i/API $2/ cpe:/a:acarsd:acarsd:$1/
+match acmp m|^ACMP Server Version ([\w._-]+)\r\n| p/Aagon ACMP Inventory/ v/$1/
+
+match apachemq m|^\0\0..\x01ActiveMQ\0\0\0.\x01\0\0.*\x0cProviderName\t\0\x08ActiveMQ.*\x0fPlatformDetails\t..JVM: (\d[^,]*), [^,]*, Oracle Corporation, OS: Linux, (\d\.[\d.]+)[^,]*, ([\w_-]+).*\x0fProviderVersion\t..(\d[\w._-]*)|s p/ActiveMQ OpenWire transport/ v/$4/ i/Java $1; arch: $3/ o/Linux $2/ cpe:/a:apache:activemq:$4/ cpe:/o:linux:linux_kernel:$2/a
+softmatch apachemq m|^\0\0..\x01ActiveMQ\0| p/ActiveMQ OpenWire transport/
+
+
+# Microsoft ActiveSync Version 3.7 Build 3083 (It's used for syncing
+# my ipaq it disappears when you remove the ipaq.)
+match activesync m|^.\0\x01\0[^\0]\0[^\0]\0[^\0]\0[^\0]\0[^\0]\0.*\0\0\0$|s p/Microsoft ActiveSync/ o/Windows/ cpe:/a:microsoft:activesync/ cpe:/o:microsoft:windows/a
+match activesync m|^\(\0\0\0\x02\0\0\0\x03\0\0\0\+\0\0\x003\0\0\0\0\0\0\0\x04\0\0`\x01\0\0\xff\0\0\0\0\0\0\0\0\0\0\0$|s p/Citrix ActiveSync/ o/Windows/ cpe:/o:microsoft:windows/a
+
+match adabas-d m|^Adabas D Remote Control Server Version ([\d.]+) Date [\d-]+ \(key is [0-9a-f]+\)\r\nOK> | p/Adabas D database remote control/ v/$1/
+
+match adobe-crossdomain m|^500 Internal Server Error
\r\n\r\n\r\n| p/Cisco Catalyst http config/ d/switch/ o/IOS/ cpe:/o:cisco:ios/a
+match http m|^HTTP/1\.1 200 OK\nMax-Age: 0\nExpires: 0\nCache-Control: no-cache\nCache-Control: private\nPragma: no-cache\nContent-type: multipart/x-mixed-replace;boundary=BoundaryString\n\n--BoundaryString\n| p/Motion Webcam gateway httpd/
+match http m|^HTTP/1\.[01] 200 OK\r\nServer: Motion/([\d.]+)\r\n| p/Motion Camera httpd/ v/$1/ d/webcam/
+match http m|^HTTP/1\.1 200 OK\r\nServer: Motion-httpd/([\d.]+)\r\n| p/Motion-httpd/ v/$1/ d/webcam/
+match http m|^HTTP/1\.1 \d\d\d .*\nServer: Motion/([\d.]+)\n.*\nContent-type: image/jpeg\n|s p/Motion webcam httpd/ v/$1/
+match http m|^HTTP/1\.1 \d\d\d .*\r\nContent-Type: text/plain\r\nServer: WPA/([-\w_.]+)\r\n\r\n| p/Glucose WeatherPop Advanced httpd/ v/$1/ o/Mac OS X/ cpe:/o:apple:mac_os_x/a
+match http m|^HTTP/1\.0 503 R\r\nContent-Type: text/html\r\n\r\nBusy$| p/D-Link router http config/ d/router/
+match http m|^501 Not Implemented
\nThe server has not implemented your request type\.
\n\r\n$| p/Hummingbird Document Manager httpd/
+match http m|^HTTP/1\.0 200 OK\r\nContent-Type: text/html\r\n\r\n\n\n
\nProgramArguments\n\n
Bad Request \(Invalid Verb\)
|s p/Microsoft IIS httpd/ o/Windows/ cpe:/a:microsoft:internet_information_services/ cpe:/o:microsoft:windows/a
+match http m|^400 Bad Request
\nYour request has bad syntax or is inherently impossible to satisfy\.\n
\n\n$| p/thttpd/ v/$1/ i/Asotel Vector 1908 switch http config/ d/switch/ cpe:/a:acme:thttpd:$1/
+match http m|^HTTP/1\.1 200 OK\r\nServer: DVBViewer \(Windows\)\r\nContent-Type: video/mpeg2\r\n\r\n\r\n| p/DVBViewer digital TV viewer httpd/ o/Windows/ cpe:/o:microsoft:windows/a
+match http m|^HTTP/1\.1 400 Bad Request\r\nserver: kolibri-([\w._-]+)\r\ncontent-type: text/plain\r\ncontent-length: 11\r\n\r\nBad Request$| p/Kolibri httpd/ v/$1/ cpe:/a:senkas:kolibri:$1/
+match http m|^HTTP/1\.1 405 Method Not Allowed\r\nServer: remote-potato-v([\w._-]+)\r\n| p/Remote Potato media player/ v/$1/
+# The date reveals the time zone instead of using GMT.
+match http m|^HTTP/1\.1 405 Method Not Allowed\r\nDate: ([^\r]+)\r\nServer: Embedthis-Appweb/([\w._-]+)\r\n| p/Embedthis-Appweb/ v/$2/ i/date: $1/ cpe:/a:mbedthis:appweb:$2/
+match http m|^HTTP/1\.0 503 Service Unavailable\r\nDate: .* GMT\r\nServer: Embedthis-Appweb/([\w._-]+)\r\n| p/Embedthis-Appweb/ v/$1/ i/Sharp Open System Architecture/ d/printer/ cpe:/a:mbedthis:appweb:$1/
+match http m|^HTTP/1\.1 400 Bad Request\r\nServer: Microsoft-Cassini/([\w._-]+)\r\n| p/Microsoft Cassini httpd/ v/$1/ o/Windows/ cpe:/a:microsoft:cassini:$1/ cpe:/o:microsoft:windows/a
+match http m|^HTTP/1\.1 408 Request Timeout\r\nServer: WebSphere Application Server/([\w._-]+)\r\nContent-Type: text/html\r\nContent-Length: 117\r\n| p/IBM WebSphere Application Server/ v/$1/ cpe:/a:ibm:websphere_application_server:$1/
+match http m|^HTTP/1\.0 200 Ok Welcome to VOC\r\nServer: Voodoo chat daemon ver ([\w._ -]+)\r\nContent-type: text/html\r\nExpires: Mon, 08 Apr 1976 19:30:00 GMT\+3\r\nConnection: close\r\nKeep-Alive: max=0\r\nCache-Control: no-store, no-cache, must-revalidate\r\nCache-Control: post-check=0, pre-check=0\r\nPragma: no-cache\r\n\r\n$| p/Voodoo http chat daemon/ v/$1/
+match http m|^HTTP/1\.1 400 Bad Request\r\nServer: Cassini/([\w._-]+)\r\n.*\n\nInvalid Access
\n
The service is not available\. Please try again later\.
$| p/Alcatel-Lucent OmniPCX PBX httpd/ d/PBX/ cpe:/a:alcatel-lucent:omnipcx/ +match http m|^HTTP/1\.0 401 Unauthorized\r\nServer: \r\nDate: .* GMT\r\nWWW-Authenticate: Basic realm=\"\.\"\r\nContent-type: text/html\r\nConnection: close\r\n\r\nAccess to this document requires a User ID
\r\n\r\n$|s p/Alvarion-Webs/ i/Alvarion BreezeMAX WiMAX WAP http config/ d/WAP/ +match http m|^HTTP/1\.0 400 Bad Request\r\nPragma: no-cache\r\nContent-type: text/html\r\n\r\n\n \n\r\n
\nReturn to last page
\n\n\n\n$|s p/AudioCodes Mediant 200 VoIP gateway http config/ d/VoIP adapter/ cpe:/a:allegro:rompager:$1/ cpe:/h:audiocodes:mediant_200/a +match http m|^HTTP/1\.1 200 OK\r\nServer: WHC chatroom\r\n| p/Fifi chat server http interface/ +match http m|^HTTP/1\.0 200 OK\r\nServer: Xunlei Http Server/([\d.]+)\r\n| p/Xunlei BitTorrent http interface/ v/$1/ +match http m|^HTTP/1\.1 200 OK\r\n.*<\?xml version=\"1\.0\" encoding=\"utf-8\"\?>\n\n\n
\n \r\n\r\n\r\n\r\n