diff --git a/README.md b/README.md index 1e0cdf6..238223e 100644 --- a/README.md +++ b/README.md @@ -37,8 +37,8 @@ - [x] Hook小程序,动态调试,开启小程序F12 - [x] 重新打包wxapkg,可破解小程序 - [x] 监听将要打包的文件夹,并自动打包 +- [x] 敏感数据导出 - [ ] 支持小游戏 -- [ ] 敏感数据导出 ### 工程结构还原 @@ -130,7 +130,7 @@ ## 用法 > -id=<输入AppID> -in=<输入文件1,输入文件2> 或 -in=<输入目录> -out=<输出目录> -> [-ext=<文件后缀>] [-restore] [-pretty] [-noClean] [-help] [-hook] [-save] [-repack=<输入目录>] [-watch] +> [-ext=<文件后缀>] [-restore] [-pretty] [-noClean] [-help] [-hook] [-save] [-repack=<输入目录>] [-watch] [-sensitive] ### 参数说明 - `-id string` @@ -165,6 +165,8 @@ - **注意:目前仅支持一次打包一个文件,同时仅支持未被解析的源文件(未使用-restore)** - `-watch` - 是否监听将要打包的文件夹,并自动打包,默认不监听 +- `-sensitive` + - 是否导出敏感数据,默认不导出 - `-help` - 显示帮助信息 diff --git a/cmd/root.go b/cmd/root.go index 0785084..e06abf1 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -9,7 +9,7 @@ import ( "github.com/Ackites/KillWxapkg/internal/restore" ) -func Execute(appID, input, outputDir, fileExt string, restoreDir bool, pretty bool, noClean bool, save bool) { +func Execute(appID, input, outputDir, fileExt string, restoreDir bool, pretty bool, noClean bool, save bool, sensitive bool) { // 存储配置 configManager := NewSharedConfigManager() configManager.Set("appID", appID) @@ -20,6 +20,7 @@ func Execute(appID, input, outputDir, fileExt string, restoreDir bool, pretty bo configManager.Set("pretty", pretty) configManager.Set("noClean", noClean) configManager.Set("save", save) + configManager.Set("sensitive", sensitive) inputFiles := ParseInput(input, fileExt) diff --git a/go.mod b/go.mod index 37cb080..d74fa04 100644 --- a/go.mod +++ b/go.mod @@ -11,6 +11,7 @@ require ( golang.org/x/crypto v0.26.0 golang.org/x/net v0.28.0 golang.org/x/text v0.17.0 + gopkg.in/yaml.v3 v3.0.1 ) require ( diff --git a/go.sum b/go.sum index ba63129..51e716f 100644 --- a/go.sum +++ b/go.sum @@ -26,5 +26,9 @@ golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM= golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/key/key.go b/internal/key/key.go new file mode 100644 index 0000000..8197c9d --- /dev/null +++ b/internal/key/key.go @@ -0,0 +1,101 @@ +package key + +import ( + "fmt" + "os" + "path/filepath" + + "gopkg.in/yaml.v3" +) + +type Rule struct { + Id string `yaml:"id"` + Enabled bool `yaml:"enabled"` + Pattern string `yaml:"pattern"` +} + +type Rules struct { + Rules []Rule `yaml:"rules"` +} + +func init() { + configDir := "config" + configFile := filepath.Join(configDir, "rule.yaml") + + if _, err := os.Stat(configFile); os.IsNotExist(err) { + if err := os.MkdirAll(configDir, 0755); err != nil { + fmt.Printf("Error creating config directory: %v\n", err) + return + } + CreateConfigFile() + } +} + +func ReadRuleFile() (*Rules, error) { + configFile := filepath.Join("config", "rule.yaml") + file, err := os.ReadFile(configFile) + if err != nil { + return nil, fmt.Errorf("error reading rule file: %v", err) + } + + var rules Rules + if err := yaml.Unmarshal(file, &rules); err != nil { + return nil, fmt.Errorf("error unmarshalling rule file: %v", err) + } + + return &rules, nil +} + +func CreateConfigFile() { + configFile := filepath.Join("config", "rule.yaml") + defaultRules := Rules{ + Rules: []Rule{ + {Id: "domain", Enabled: false, Pattern: ""}, + {Id: "path", Enabled: false, Pattern: ""}, + {Id: "domain_url", Enabled: false, Pattern: ""}, + {Id: "ip", Enabled: false, Pattern: ""}, + {Id: "ip_url", Enabled: false, Pattern: `\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}`}, + {Id: "email", Enabled: true, Pattern: `\b[A-Za-z0-9._\-]+@[A-Za-z0-9.\-]+\.[A-Za-z]{2,61}\b`}, + {Id: "id_card", Enabled: true, Pattern: `\b([1-9]\d{5}(19|20)\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx])\b`}, + {Id: "phone", Enabled: true, Pattern: `\b1[3-9]\d{9}\b`}, + {Id: "jwt_token", Enabled: true, Pattern: `eyJ[A-Za-z0-9_/+\-]{10,}={0,2}\.[A-Za-z0-9_/+\-\\]{15,}={0,2}\.[A-Za-z0-9_/+\-\\]{10,}={0,2}`}, + {Id: "Aliyun_AK_ID", Enabled: true, Pattern: `\bLTAI[A-Za-z\d]{12,30}\b`}, + {Id: "QCloud_AK_ID", Enabled: true, Pattern: `\bAKID[A-Za-z\d]{13,40}\b`}, + {Id: "JDCloud_AK_ID", Enabled: true, Pattern: `\bJDC_[0-9A-Z]{25,40}\b`}, + {Id: "AWS_AK_ID", Enabled: true, Pattern: `["''](?:A3T[A-Z0-9]|AKIA|AGPA|AIDA|AROA|AIPA|ANPA|ANVA|ASIA)[A-Z0-9]{16}["'']`}, + {Id: "VolcanoEngine_AK_ID", Enabled: true, Pattern: `\b(?:AKLT|AKTP)[a-zA-Z0-9]{35,50}\b`}, + {Id: "Kingsoft_AK_ID", Enabled: true, Pattern: `\bAKLT[a-zA-Z0-9-_]{16,28}\b`}, + {Id: "GCP_AK_ID", Enabled: true, Pattern: `\bAIza[0-9A-Za-z_\-]{35}\b`}, + {Id: "secret_key", Enabled: true, Pattern: ""}, + {Id: "bearer_token", Enabled: true, Pattern: `\b[Bb]earer\s+[a-zA-Z0-9\-=._+/\\]{20,500}\b`}, + {Id: "basic_token", Enabled: true, Pattern: `\b[Bb]asic\s+[A-Za-z0-9+/]{18,}={0,2}\b`}, + {Id: "auth_token", Enabled: true, Pattern: `["''\[]*[Aa]uthorization["''\]]*\s*[:=]\s*[''"]?\b(?:[Tt]oken\s+)?[a-zA-Z0-9\-_+/]{20,500}[''"]?`}, + {Id: "private_key", Enabled: true, Pattern: `-----\s*?BEGIN[ A-Z0-9_-]*?PRIVATE KEY\s*?-----[a-zA-Z0-9\/\n\r=+]*-----\s*?END[ A-Z0-9_-]*? PRIVATE KEY\s*?-----`}, + {Id: "gitlab_v2_token", Enabled: true, Pattern: `\b(glpat-[a-zA-Z0-9\-=_]{20,22})\b`}, + {Id: "github_token", Enabled: true, Pattern: `\b((?:ghp|gho|ghu|ghs|ghr|github_pat)_[a-zA-Z0-9_]{36,255})\b`}, + {Id: "qcloud_api_gateway_appkey", Enabled: true, Pattern: `\bAPID[a-zA-Z0-9]{32,42}\b`}, + {Id: "wechat_appid", Enabled: true, Pattern: `["''](wx[a-z0-9]{15,18})["'']`}, + {Id: "wechat_corpid", Enabled: true, Pattern: `["''](ww[a-z0-9]{15,18})["'']`}, + {Id: "wechat_id", Enabled: true, Pattern: `["''](gh_[a-z0-9]{11,13})["'']`}, + {Id: "password", Enabled: true, Pattern: `(?i)(?:admin_?pass|password|[a-z]{3,15}_?password|user_?pass|user_?pwd|admin_?pwd)\\?['"]*\s*[:=]\s*\\?['"][a-z0-9!@#$%&*]{5,50}\\?['"]`}, + {Id: "wechat_webhookurl", Enabled: true, Pattern: `\bhttps://qyapi.weixin.qq.com/cgi-bin/webhook/send\?key=[a-zA-Z0-9\-]{25,50}\b`}, + {Id: "dingtalk_webhookurl", Enabled: true, Pattern: `\bhttps://oapi.dingtalk.com/robot/send\?access_token=[a-z0-9]{50,80}\b`}, + {Id: "feishu_webhookurl", Enabled: true, Pattern: `\bhttps://open.feishu.cn/open-apis/bot/v2/hook/[a-z0-9\-]{25,50}\b`}, + {Id: "slack_webhookurl", Enabled: true, Pattern: `\bhttps://hooks.slack.com/services/[a-zA-Z0-9\-_]{6,12}/[a-zA-Z0-9\-_]{6,12}/[a-zA-Z0-9\-_]{15,24}\b`}, + {Id: "grafana_api_key", Enabled: true, Pattern: `\beyJrIjoi[a-zA-Z0-9\-_+/]{50,100}={0,2}\b`}, + {Id: "grafana_cloud_api_token", Enabled: true, Pattern: `\bglc_[A-Za-z0-9\-_+/]{32,200}={0,2}\b`}, + {Id: "grafana_service_account_token", Enabled: true, Pattern: `\bglsa_[A-Za-z0-9]{32}_[A-Fa-f0-9]{8}\b`}, + {Id: "app_key", Enabled: true, Pattern: `\b(?:VUE|APP|REACT)_[A-Z_0-9]{1,15}_(?:KEY|PASS|PASSWORD|TOKEN|APIKEY)['"]*[:=]"(?:[A-Za-z0-9_\-]{15,50}|[a-z0-9/+]{50,100}==?)"`}, + }, + } + + data, err := yaml.Marshal(&defaultRules) + if err != nil { + fmt.Printf("Error marshalling default rules: %v\n", err) + return + } + + if err := os.WriteFile(configFile, data, 0755); err != nil { + fmt.Printf("Error writing default rule file: %v\n", err) + } +} diff --git a/internal/key/match.go b/internal/key/match.go new file mode 100644 index 0000000..9c9a909 --- /dev/null +++ b/internal/key/match.go @@ -0,0 +1,82 @@ +package key + +import ( + "encoding/json" + "fmt" + "os" + "regexp" + "strings" + "sync" +) + +var ( + rulesInstance *Rules + once sync.Once + jsonMutex sync.Mutex +) + +func getRulesInstance() (*Rules, error) { + var err error + once.Do(func() { + rulesInstance, err = ReadRuleFile() + }) + return rulesInstance, err +} + +func MatchRules(input string) error { + rules, err := getRulesInstance() + if err != nil { + return fmt.Errorf("%v", err) + } + + for _, rule := range rules.Rules { + if rule.Enabled { + re, err := regexp.Compile(rule.Pattern) + if err != nil { + return fmt.Errorf("failed to compile regex for rule %s: %v", rule.Id, err) + } + matches := re.FindAllStringSubmatch(input, -1) + for _, match := range matches { + if len(match) > 0 { + if strings.TrimSpace(match[0]) == "" { + continue + } + err := appendToJSON(rule.Id, match[0]) + if err != nil { + return fmt.Errorf("failed to append to JSON: %v", err) + } + } + } + } + } + + return nil +} + +func appendToJSON(ruleId, matchedContent string) error { + jsonMutex.Lock() + defer jsonMutex.Unlock() + + file, err := os.OpenFile("sensitive_data.json", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + if err != nil { + return fmt.Errorf("failed to open JSON file: %v", err) + } + defer func(file *os.File) { + err := file.Close() + if err != nil { + fmt.Printf("failed to close JSON file: %v", err) + } + }(file) + + record := map[string]string{ + "rule_id": ruleId, + "content": matchedContent, + } + + encoder := json.NewEncoder(file) + if err := encoder.Encode(record); err != nil { + return fmt.Errorf("failed to write to JSON file: %v", err) + } + + return nil +} diff --git a/internal/unpack/unpack.go b/internal/unpack/unpack.go index 0642c90..b2b8126 100644 --- a/internal/unpack/unpack.go +++ b/internal/unpack/unpack.go @@ -10,6 +10,10 @@ import ( "path/filepath" "sync" + "github.com/Ackites/KillWxapkg/internal/key" + + "github.com/Ackites/KillWxapkg/internal/config" + formatter2 "github.com/Ackites/KillWxapkg/internal/formatter" ) @@ -221,5 +225,17 @@ func processFile(outputDir string, file WxapkgFile, reader io.ReaderAt, bufferPo return fmt.Errorf("写入文件失败: %w", err) } + configManager := config.NewSharedConfigManager() + if sensitive, ok := configManager.Get("sensitive"); ok { + if p, o := sensitive.(bool); o { + if p { + // 查找敏感信息 + if err := key.MatchRules(string(content)); err != nil { + return fmt.Errorf("%v", err) + } + } + } + } + return nil } diff --git a/main.go b/main.go index 698f64c..915d2ac 100644 --- a/main.go +++ b/main.go @@ -22,6 +22,7 @@ var ( save bool repack string watch bool + sensitive bool ) func init() { @@ -36,6 +37,7 @@ func init() { flag.BoolVar(&save, "save", false, "是否保存解密后的文件") flag.StringVar(&repack, "repack", "", "重新打包wxapkg文件") flag.BoolVar(&watch, "watch", false, "是否监听将要打包的文件夹,并自动打包") + flag.BoolVar(&sensitive, "sensitive", false, "是否获取敏感数据") } func main() { @@ -67,12 +69,12 @@ func main() { } if appID == "" || input == "" { - fmt.Println("使用方法: program -id= -in=<输入文件1,输入文件2> 或 -in=<输入目录> -out=<输出目录> [-ext=<文件后缀>] [-restore] [-pretty] [-noClean] [-hook] [-save] [-repack=<输入目录>] [-watch]") + fmt.Println("使用方法: program -id= -in=<输入文件1,输入文件2> 或 -in=<输入目录> -out=<输出目录> [-ext=<文件后缀>] [-restore] [-pretty] [-noClean] [-hook] [-save] [-repack=<输入目录>] [-watch] [-sensitive]") flag.PrintDefaults() fmt.Println() return } // 执行命令 - cmd.Execute(appID, input, outputDir, fileExt, restoreDir, pretty, noClean, save) + cmd.Execute(appID, input, outputDir, fileExt, restoreDir, pretty, noClean, save, sensitive) }