├── .gitignore ├── .goreleaser.yaml ├── LICENSE ├── README.md ├── api ├── appidtoname.go ├── config.go └── filemonitor.go ├── cmd └── root.go ├── config ├── config.yaml └── rule.yaml ├── go.mod ├── go.sum ├── image ├── img.png └── img_1.png ├── internal ├── cmd │ └── cmd.go ├── config │ ├── delete.go │ ├── share.go │ └── wxapkg.go ├── decrypt │ └── decrypt.go ├── enum │ └── wxapkg.go ├── formatter │ ├── formatter.go │ ├── htmlformatter.go │ ├── jsformatter.go │ └── jsonformatter.go ├── hook │ ├── embed_other.go │ ├── embed_windows.go │ ├── hook.go │ └── win.exe ├── key │ ├── 800+ 规则.yml │ ├── key.go │ └── match.go ├── pack │ └── pack.go ├── restore │ ├── decompiler.go │ ├── parser.go │ └── restore.go ├── unpack │ ├── uconfig.go │ ├── ujs.go │ ├── unpack.go │ ├── uxml.go │ └── uxss.go └── util │ ├── getWccVersion.go │ ├── getWxapkgType.go │ ├── humanReadableSize.go │ └── transformCSS.go └── main.go /.gitignore: -------------------------------------------------------------------------------- 1 | ### GoLand+all template 2 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 3 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 4 | 5 | # User-specific stuff 6 | .idea/**/workspace.xml 7 | .idea/**/tasks.xml 8 | .idea/**/usage.statistics.xml 9 | .idea/**/dictionaries 10 | .idea/**/shelf 11 | 12 | # AWS User-specific 13 | .idea/**/aws.xml 14 | 15 | # Generated files 16 | .idea/**/contentModel.xml 17 | 18 | # Sensitive or high-churn files 19 | .idea/**/dataSources/ 20 | .idea/**/dataSources.ids 21 | .idea/**/dataSources.local.xml 22 | .idea/**/sqlDataSources.xml 23 | .idea/**/dynamic.xml 24 | .idea/**/uiDesigner.xml 25 | .idea/**/dbnavigator.xml 26 | 27 | # Gradle 28 | .idea/**/gradle.xml 29 | .idea/**/libraries 30 | 31 | # Gradle and Maven with auto-import 32 | # When using Gradle or Maven with auto-import, you should exclude module files, 33 | # since they will be recreated, and may cause churn. Uncomment if using 34 | # auto-import. 35 | # .idea/artifacts 36 | # .idea/compiler.xml 37 | # .idea/jarRepositories.xml 38 | # .idea/modules.xml 39 | # .idea/*.iml 40 | # .idea/modules 41 | # *.iml 42 | # *.ipr 43 | 44 | # CMake 45 | cmake-build-*/ 46 | 47 | # Mongo Explorer plugin 48 | .idea/**/mongoSettings.xml 49 | 50 | # File-based project format 51 | *.iws 52 | 53 | # IntelliJ 54 | out/ 55 | 56 | # mpeltonen/sbt-idea plugin 57 | .idea_modules/ 58 | 59 | # JIRA plugin 60 | atlassian-ide-plugin.xml 61 | 62 | # Cursive Clojure plugin 63 | .idea/replstate.xml 64 | 65 | # SonarLint plugin 66 | .idea/sonarlint/ 67 | 68 | # Crashlytics plugin (for Android Studio and IntelliJ) 69 | com_crashlytics_export_strings.xml 70 | crashlytics.properties 71 | crashlytics-build.properties 72 | fabric.properties 73 | 74 | # Editor-based Rest Client 75 | .idea/httpRequests 76 | 77 | # Android studio 3.1+ serialized cache file 78 | .idea/caches/build_file_checksums.ser 79 | 80 | ### Go template 81 | # If you prefer the allow list template instead of the deny list, see community template: 82 | # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore 83 | # 84 | # Binaries for programs and plugins 85 | *.dll 86 | *.so 87 | *.dylib 88 | 89 | # Test binary, built with `go test -c` 90 | *.test 91 | 92 | # Output of the go coverage tool, specifically when used with LiteIDE 93 | *.out 94 | out/* 95 | 96 | # Dependency directories (remove the comment below to include it) 97 | # vendor/ 98 | 99 | # Go workspace file 100 | go.work 101 | 102 | ### GoLand template 103 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 104 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 105 | 106 | # User-specific stuff 107 | 108 | # AWS User-specific 109 | 110 | # Generated files 111 | 112 | # Sensitive or high-churn files 113 | 114 | # Gradle 115 | 116 | # Gradle and Maven with auto-import 117 | # When using Gradle or Maven with auto-import, you should exclude module files, 118 | # since they will be recreated, and may cause churn. Uncomment if using 119 | # auto-import. 120 | # .idea/artifacts 121 | # .idea/compiler.xml 122 | # .idea/jarRepositories.xml 123 | # .idea/modules.xml 124 | # .idea/*.iml 125 | # .idea/modules 126 | # *.iml 127 | # *.ipr 128 | 129 | # CMake 130 | 131 | # Mongo Explorer plugin 132 | 133 | # File-based project format 134 | 135 | # IntelliJ 136 | 137 | # mpeltonen/sbt-idea plugin 138 | 139 | # JIRA plugin 140 | 141 | # Cursive Clojure plugin 142 | 143 | # SonarLint plugin 144 | 145 | # Crashlytics plugin (for Android Studio and IntelliJ) 146 | 147 | # Editor-based Rest Client 148 | 149 | # Android studio 3.1+ serialized cache file 150 | 151 | ### GoLand+iml template 152 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 153 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 154 | 155 | # User-specific stuff 156 | 157 | # AWS User-specific 158 | 159 | # Generated files 160 | 161 | # Sensitive or high-churn files 162 | 163 | # Gradle 164 | 165 | # Gradle and Maven with auto-import 166 | # When using Gradle or Maven with auto-import, you should exclude module files, 167 | # since they will be recreated, and may cause churn. Uncomment if using 168 | # auto-import. 169 | # .idea/artifacts 170 | # .idea/compiler.xml 171 | # .idea/jarRepositories.xml 172 | # .idea/modules.xml 173 | # .idea/*.iml 174 | # .idea/modules 175 | # *.iml 176 | # *.ipr 177 | 178 | # CMake 179 | 180 | # Mongo Explorer plugin 181 | 182 | # File-based project format 183 | 184 | # IntelliJ 185 | 186 | # mpeltonen/sbt-idea plugin 187 | 188 | # JIRA plugin 189 | 190 | # Cursive Clojure plugin 191 | 192 | # SonarLint plugin 193 | 194 | # Crashlytics plugin (for Android Studio and IntelliJ) 195 | 196 | # Editor-based Rest Client 197 | 198 | # Android studio 3.1+ serialized cache file 199 | 200 | 201 | dist/ 202 | .idea/ 203 | -------------------------------------------------------------------------------- /.goreleaser.yaml: -------------------------------------------------------------------------------- 1 | # This is an example .goreleaser.yml file with some sensible defaults. 2 | # Make sure to check the documentation at https://goreleaser.com 3 | 4 | # The lines below are called `modelines`. See `:help modeline` 5 | # Feel free to remove those if you don't want/need to use them. 6 | # yaml-language-server: $schema=https://goreleaser.com/static/schema.json 7 | # vim: set ts=2 sw=2 tw=0 fo=cnqoj 8 | 9 | version: 2 10 | 11 | before: 12 | hooks: 13 | # You may remove this if you don't use go modules. 14 | - go mod tidy 15 | 16 | upx: 17 | - enabled: true 18 | compress: best 19 | lzma: true 20 | 21 | builds: 22 | - env: 23 | - CGO_ENABLED=0 24 | goos: 25 | - windows 26 | - darwin 27 | 28 | archives: 29 | - format: binary 30 | 31 | changelog: 32 | sort: asc 33 | filters: 34 | exclude: 35 | - "^docs:" 36 | - "^test:" 37 | - "^style:" 38 | - "^ci:" 39 | - "^perf:" 40 | - "^build:" 41 | - "^deps:" 42 | - "^vendor:" 43 | - "^internal:" 44 | - "^examples:" 45 | 46 | checksum: 47 | algorithm: md5 -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Antkites 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # KillWxapkg-Auto 2 | 3 | > 二开KillWxapkg,自动化监控实时点击打开的微信小程序进行反编译,辅助小程序渗透测试。 4 | > 原作者:https://github.com/Ackites/KillWxapkg 5 | 6 | 7 | ------------------ 8 | ## 声明 9 | 10 | **本程序仅供于学习交流,请使用者遵守《中华人民共和国网络安全法》,勿将此工具用于非授权的测试,开发者不负任何连带法律责任。** 11 | 12 | **核心抄的,只是个自动化;没啥技术难度,大哥们别来提有关核心的issue,我只是个搬运工。** 13 | 14 | ## 二开 15 | 16 | > 思路原本是24年上半年就想到,后面由于各种原因忘了。后面听了小迪直播中艾克sec的分享,同为22期学员差距怎么这么大。现在看来还是自己懒了。 17 | > 想到了《士兵突击》团长对许三多说的话: 18 | > “想到和得到的中间,还有两个字:做到!” 19 | 20 | 1. 【完成】自动监控实时点击打开的微信小程序进行反编译,并输出反编译后的代码。 21 | 22 | 2. 【完成】将微信appid查询转换为微信小程序名字,方便后续查找泄露文件点复现漏洞。(需要联网) 23 | 24 | 3. 【废弃】~~优化原作者敏感信息查找规则以及方式。~~ 发现原作者规则更改很简单,config/rule.yaml 自定义即可。(把email、phone、wxid等设置false减少干扰,另外修改原版敏感信息匹配输出json为html) 25 | 26 | ![img.png](image/img.png) 27 | 28 | 4. 【计划】改成WEBUI界面,方便使用。(问为啥不改成客户端,就是说个人感觉打开界面太多了会太杂乱,不如一个浏览器一个窗口) 29 | 30 | ## 介绍 31 | >主要新增KillWxapkg-Auto.exe -auto用法, 其余用法参照原作者。 32 | 33 | config/config.yaml 配置小程序目录绝对路径 34 | 35 | > KillWxapkg-Auto.exe -auto 36 | 37 | ![img_1.png](image%2Fimg_1.png) 38 | 39 | 40 | **有时候会编译失败找不到文件,这里多是小程序太大网络环境较慢导致,可以等一会小程序对应文件加载完删掉Ctrl+Z重新进行反编译。** 41 | ```shell 42 | $ KillWxapkg-Auto.exe --help 43 | 44 | -auto 45 | 是否目录监控自动反编译,点击即是反编译 46 | -ext string 47 | 处理的文件后缀 (default ".wxapkg") 48 | -hook 49 | 是否开启动态调试 50 | -id string 51 | 微信小程序的AppID 52 | -in string 53 | 输入文件路径(多个文件用逗号分隔)或输入目录路径 54 | -noClean 55 | 是否清理中间文件 56 | -out string 57 | 输出目录路径(如果未指定,则默认保存到输入目录下以AppID命名的文件夹) 58 | -pretty 59 | 是否美化输出 60 | -repack string 61 | 重新打包wxapkg文件 62 | -restore 63 | 是否还原工程目录结构 64 | -save 65 | 是否保存解密后的文件 66 | -sensitive 67 | 是否获取敏感数据 68 | -watch 69 | 是否监听将要打包的文件夹,并自动打包 70 | ``` 71 | -------------------------------------------------------------------------------- /api/appidtoname.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io/ioutil" 7 | "net/http" 8 | ) 9 | 10 | // 定义一个结构体来映射JSON数据 11 | type ResponseData struct { 12 | Code int `json:"code"` 13 | Data struct { 14 | Nickname string `json:"nickname"` 15 | Username string `json:"username"` 16 | Description string `json:"description"` 17 | Avatar string `json:"avatar"` 18 | AppID string `json:"appid"` 19 | UsesCount string `json:"uses_count"` 20 | PrincipalName string `json:"principal_name"` 21 | } `json:"data"` 22 | } 23 | 24 | // 请求函数 25 | func SendRequest(appID string) ([]byte, error) { 26 | // 目标URL, 对应appid查询小程序 27 | url := "https://kainy.cn/api/weapp/info/" 28 | 29 | // 创建POST请求 30 | req, err := http.NewRequest("POST", url, bytes.NewBuffer([]byte("appid="+appID))) 31 | if err != nil { 32 | return nil, fmt.Errorf("创建请求失败: %v", err) 33 | } 34 | 35 | // 设置请求头 36 | req.Header.Set("Host", "kainy.cn") 37 | req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36") 38 | req.Header.Set("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8") 39 | req.Header.Set("Accept-Encoding", "gzip, deflate, br") 40 | req.Header.Set("Connection", "keep-alive") 41 | 42 | // 发送请求 43 | client := &http.Client{} 44 | resp, err := client.Do(req) 45 | if err != nil { 46 | return nil, fmt.Errorf("发送请求失败: %v", err) 47 | } 48 | defer resp.Body.Close() 49 | 50 | // 读取响应 51 | return ioutil.ReadAll(resp.Body) 52 | } 53 | 54 | //func main() { 55 | // // 调用请求函数 56 | // body, err := SendRequest("wx310cecc58fa78fc6") 57 | // if err != nil { 58 | // fmt.Println(err) 59 | // return 60 | // } 61 | // 62 | // // 输出状态码 63 | // fmt.Println("响应内容:", string(body)) 64 | // 65 | // // 解析JSON响应 66 | // var responseData ResponseData 67 | // if err := json.Unmarshal(body, &responseData); err != nil { 68 | // fmt.Println("解析JSON失败:", err) 69 | // return 70 | // } 71 | // 72 | // // 提取name字段 73 | // fmt.Println("名称:", responseData.Data.Nickname) 74 | //} 75 | -------------------------------------------------------------------------------- /api/config.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "errors" 5 | "gopkg.in/yaml.v2" 6 | "io/ioutil" 7 | ) 8 | 9 | // Config 用于映射 YAML 文件的结构 10 | type Config struct { 11 | WxAppletPath string `yaml:"wx_applet_path"` 12 | WxOutputPath string `yaml:"wx_output_path"` 13 | TimeOut int `yaml:"time_out"` 14 | } 15 | 16 | // LoadConfig 读取 YAML 文件并返回配置 17 | func LoadConfig(filePath string) (*Config, error) { 18 | if filePath == "" { 19 | return nil, errors.New("file path cannot be empty") 20 | } 21 | 22 | data, err := ioutil.ReadFile(filePath) 23 | if err != nil { 24 | return nil, err 25 | } 26 | 27 | var config Config 28 | err = yaml.Unmarshal(data, &config) 29 | if err != nil { 30 | return nil, err 31 | } 32 | 33 | return &config, nil 34 | } 35 | -------------------------------------------------------------------------------- /api/filemonitor.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "fmt" 5 | "github.com/fsnotify/fsnotify" 6 | "log" 7 | "os" 8 | "path/filepath" 9 | "strings" 10 | ) 11 | 12 | // 监控目录并通过通道返回新增的文件名 13 | func WatchNewFiles(directory string, fileChan chan<- string) error { 14 | // 创建一个文件系统监听器 15 | watcher, err := fsnotify.NewWatcher() 16 | if err != nil { 17 | return err 18 | } 19 | defer watcher.Close() 20 | 21 | // 开始监控指定的目录 22 | err = watcher.Add(directory) 23 | if err != nil { 24 | return err 25 | } 26 | 27 | // 持续监听文件系统事件 28 | for { 29 | select { 30 | case event := <-watcher.Events: 31 | // 检测到新文件被创建 32 | if event.Op&fsnotify.Create == fsnotify.Create { 33 | //fmt.Printf("新文件: %s\n", event.Name) 34 | //fmt.Println(filepath.Base(event.Name)) 35 | // 将新增的文件名发送到通道中 36 | if strings.HasPrefix(filepath.Base(event.Name), "wx") { // 过滤掉日志文件 37 | fileChan <- event.Name 38 | } 39 | } 40 | case err := <-watcher.Errors: 41 | log.Println("监控错误:", err) 42 | } 43 | } 44 | } 45 | 46 | func ListSubdirectories(path string) ([]string, error) { 47 | entries, err := os.ReadDir(path) 48 | if err != nil { 49 | return nil, fmt.Errorf("无法读取目录: %w", err) 50 | } 51 | 52 | var subdirs []string 53 | 54 | // 遍历目录条目,筛选出子目录 55 | for _, entry := range entries { 56 | if entry.Type().IsDir() { 57 | subdirs = append(subdirs, entry.Name()) // 收集子目录名称 58 | } 59 | } 60 | 61 | return subdirs, nil 62 | } 63 | -------------------------------------------------------------------------------- /cmd/root.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "log" 5 | "sync" 6 | 7 | . "github.com/Ackites/KillWxapkg/internal/cmd" 8 | . "github.com/Ackites/KillWxapkg/internal/config" 9 | "github.com/Ackites/KillWxapkg/internal/restore" 10 | ) 11 | 12 | func Execute(appID, input, outputDir, fileExt string, restoreDir bool, pretty bool, noClean bool, save bool, sensitive bool) { 13 | // 存储配置 14 | configManager := NewSharedConfigManager() 15 | configManager.Set("appID", appID) 16 | configManager.Set("input", input) 17 | configManager.Set("outputDir", outputDir) 18 | configManager.Set("fileExt", fileExt) 19 | configManager.Set("restoreDir", restoreDir) 20 | configManager.Set("pretty", pretty) 21 | configManager.Set("noClean", noClean) 22 | configManager.Set("save", save) 23 | configManager.Set("sensitive", sensitive) 24 | 25 | inputFiles := ParseInput(input, fileExt) 26 | 27 | if len(inputFiles) == 0 { 28 | log.Println("未找到任何文件") 29 | return 30 | } 31 | 32 | // 确定输出目录 33 | if outputDir == "" { 34 | outputDir = DetermineOutputDir(input, appID) 35 | } 36 | 37 | var wg sync.WaitGroup 38 | for _, inputFile := range inputFiles { 39 | wg.Add(1) 40 | go func(file string) { 41 | defer wg.Done() 42 | err := ProcessFile(file, outputDir, appID, save) 43 | if err != nil { 44 | log.Printf("处理文件 %s 时出错: %v\n", file, err) 45 | } else { 46 | log.Printf("成功处理文件: %s\n", file) 47 | } 48 | }(inputFile) 49 | } 50 | wg.Wait() 51 | 52 | // 还原工程目录结构 53 | restore.ProjectStructure(outputDir, restoreDir) 54 | } 55 | -------------------------------------------------------------------------------- /config/config.yaml: -------------------------------------------------------------------------------- 1 | wx_applet_path: D:\appdata\tencent\WeChat_resp\WeChat Files\Applet 2 | wx_output_path: 3 | time_out: 10 4 | # time_out为小程序资源加载超时时间,单位为秒,偶尔出现加载超时的情况,可以适当调大 -------------------------------------------------------------------------------- /config/rule.yaml: -------------------------------------------------------------------------------- 1 | rules: 2 | - id: domain 3 | enabled: false 4 | pattern: "" 5 | - id: path 6 | enabled: false 7 | pattern: "" 8 | - id: domain_url 9 | enabled: false 10 | pattern: "" 11 | - id: ip 12 | enabled: false 13 | pattern: "" 14 | - id: ip_url 15 | enabled: false 16 | pattern: \d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3} 17 | - id: email 18 | enabled: false 19 | pattern: \b[A-Za-z0-9._\-]+@[A-Za-z0-9.\-]+\.[A-Za-z]{2,61}\b 20 | - id: id_card 21 | enabled: true 22 | 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 23 | - id: phone 24 | enabled: false 25 | pattern: \b1[3-9]\d{9}\b 26 | - id: jwt_token 27 | enabled: true 28 | pattern: eyJ[A-Za-z0-9_/+\-]{10,}={0,2}\.[A-Za-z0-9_/+\-\\]{15,}={0,2}\.[A-Za-z0-9_/+\-\\]{10,}={0,2} 29 | - id: Aliyun_AK_ID 30 | enabled: true 31 | pattern: \bLTAI[A-Za-z\d]{12,30}\b 32 | - id: QCloud_AK_ID 33 | enabled: true 34 | pattern: \bAKID[A-Za-z\d]{13,40}\b 35 | - id: JDCloud_AK_ID 36 | enabled: true 37 | pattern: \bJDC_[0-9A-Z]{25,40}\b 38 | - id: AWS_AK_ID 39 | enabled: true 40 | pattern: '["''''](?:A3T[A-Z0-9]|AKIA|AGPA|AIDA|AROA|AIPA|ANPA|ANVA|ASIA)[A-Z0-9]{16}["'''']' 41 | - id: VolcanoEngine_AK_ID 42 | enabled: true 43 | pattern: \b(?:AKLT|AKTP)[a-zA-Z0-9]{35,50}\b 44 | - id: Kingsoft_AK_ID 45 | enabled: true 46 | pattern: \bAKLT[a-zA-Z0-9-_]{16,28}\b 47 | - id: GCP_AK_ID 48 | enabled: true 49 | pattern: \bAIza[0-9A-Za-z_\-]{35}\b 50 | - id: secret_key 51 | enabled: true 52 | pattern: "" 53 | - id: bearer_token 54 | enabled: true 55 | pattern: \b[Bb]earer\s+[a-zA-Z0-9\-=._+/\\]{20,500}\b 56 | - id: basic_token 57 | enabled: true 58 | pattern: \b[Bb]asic\s+[A-Za-z0-9+/]{18,}={0,2}\b 59 | - id: auth_token 60 | enabled: true 61 | pattern: '["''''\[]*[Aa]uthorization["''''\]]*\s*[:=]\s*[''''"]?\b(?:[Tt]oken\s+)?[a-zA-Z0-9\-_+/]{20,500}[''''"]?' 62 | - id: private_key 63 | enabled: true 64 | pattern: '-----\s*?BEGIN[ A-Z0-9_-]*?PRIVATE KEY\s*?-----[a-zA-Z0-9\/\n\r=+]*-----\s*?END[ A-Z0-9_-]*? PRIVATE KEY\s*?-----' 65 | - id: gitlab_v2_token 66 | enabled: true 67 | pattern: \b(glpat-[a-zA-Z0-9\-=_]{20,22})\b 68 | - id: github_token 69 | enabled: true 70 | pattern: \b((?:ghp|gho|ghu|ghs|ghr|github_pat)_[a-zA-Z0-9_]{36,255})\b 71 | - id: qcloud_api_gateway_appkey 72 | enabled: true 73 | pattern: \bAPID[a-zA-Z0-9]{32,42}\b 74 | - id: wechat_appid 75 | enabled: false 76 | pattern: '["''''](wx[a-z0-9]{15,18})["'''']' 77 | - id: wechat_corpid 78 | enabled: true 79 | pattern: '["''''](ww[a-z0-9]{15,18})["'''']' 80 | - id: wechat_id 81 | enabled: true 82 | pattern: '["''''](gh_[a-z0-9]{11,13})["'''']' 83 | - id: password 84 | enabled: true 85 | pattern: (?i)(?:admin_?pass|password|[a-z]{3,15}_?password|user_?pass|user_?pwd|admin_?pwd)\\?['"]*\s*[:=]\s*\\?['"][a-z0-9!@#$%&*]{5,50}\\?['"] 86 | - id: wechat_webhookurl 87 | enabled: true 88 | pattern: \bhttps://qyapi.weixin.qq.com/cgi-bin/webhook/send\?key=[a-zA-Z0-9\-]{25,50}\b 89 | - id: dingtalk_webhookurl 90 | enabled: true 91 | pattern: \bhttps://oapi.dingtalk.com/robot/send\?access_token=[a-z0-9]{50,80}\b 92 | - id: feishu_webhookurl 93 | enabled: true 94 | pattern: \bhttps://open.feishu.cn/open-apis/bot/v2/hook/[a-z0-9\-]{25,50}\b 95 | - id: slack_webhookurl 96 | enabled: true 97 | 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 98 | - id: grafana_api_key 99 | enabled: true 100 | pattern: \beyJrIjoi[a-zA-Z0-9\-_+/]{50,100}={0,2}\b 101 | - id: grafana_cloud_api_token 102 | enabled: true 103 | pattern: \bglc_[A-Za-z0-9\-_+/]{32,200}={0,2}\b 104 | - id: grafana_service_account_token 105 | enabled: true 106 | pattern: \bglsa_[A-Za-z0-9]{32}_[A-Fa-f0-9]{8}\b 107 | - id: app_key 108 | enabled: true 109 | 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}==?)" 110 | - id: 腾讯文档 111 | enabled: true 112 | pattern: \bhttps://docs.qq.com/[a-z0-9\-]*/+[a-zA-Z0-9\-_]* 113 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/Ackites/KillWxapkg 2 | 3 | go 1.23 4 | 5 | require ( 6 | github.com/ditashi/jsbeautifier-go v0.0.0-20141206144643-2520a8026a9c 7 | github.com/dop251/goja v0.0.0-20240822155948-fa6d1ed5e4b6 8 | github.com/fsnotify/fsnotify v1.7.0 9 | github.com/tdewolff/parse/v2 v2.7.15 10 | github.com/yosssi/gohtml v0.0.0-20201013000340-ee4748c638f4 11 | golang.org/x/crypto v0.26.0 12 | golang.org/x/net v0.28.0 13 | golang.org/x/text v0.17.0 14 | gopkg.in/yaml.v2 v2.4.0 15 | gopkg.in/yaml.v3 v3.0.1 16 | ) 17 | 18 | require ( 19 | github.com/dlclark/regexp2 v1.11.4 // indirect 20 | github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect 21 | github.com/google/pprof v0.0.0-20230207041349-798e818bf904 // indirect 22 | golang.org/x/sys v0.23.0 // indirect 23 | ) 24 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0= 2 | github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= 3 | github.com/ditashi/jsbeautifier-go v0.0.0-20141206144643-2520a8026a9c h1:+Zo5Ca9GH0RoeVZQKzFJcTLoAixx5s5Gq3pTIS+n354= 4 | github.com/ditashi/jsbeautifier-go v0.0.0-20141206144643-2520a8026a9c/go.mod h1:HJGU9ULdREjOcVGZVPB5s6zYmHi1RxzT71l2wQyLmnE= 5 | github.com/dlclark/regexp2 v1.11.4 h1:rPYF9/LECdNymJufQKmri9gV604RvvABwgOA8un7yAo= 6 | github.com/dlclark/regexp2 v1.11.4/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= 7 | github.com/dop251/goja v0.0.0-20240822155948-fa6d1ed5e4b6 h1:0x8Sh2rKCTVUQnRTJFIwtRWAp91VMsnATQEsMAg14kM= 8 | github.com/dop251/goja v0.0.0-20240822155948-fa6d1ed5e4b6/go.mod h1:MxLav0peU43GgvwVgNbLAj1s/bSGboKkhuULvq/7hx4= 9 | github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= 10 | github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= 11 | github.com/go-sourcemap/sourcemap v2.1.3+incompatible h1:W1iEw64niKVGogNgBN3ePyLFfuisuzeidWPMPWmECqU= 12 | github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg= 13 | github.com/google/pprof v0.0.0-20230207041349-798e818bf904 h1:4/hN5RUoecvl+RmJRE2YxKWtnnQls6rQjjW5oV7qg2U= 14 | github.com/google/pprof v0.0.0-20230207041349-798e818bf904/go.mod h1:uglQLonpP8qtYCYyzA+8c/9qtqgA3qsXGYqCPKARAFg= 15 | github.com/tdewolff/parse/v2 v2.7.15 h1:hysDXtdGZIRF5UZXwpfn3ZWRbm+ru4l53/ajBRGpCTw= 16 | github.com/tdewolff/parse/v2 v2.7.15/go.mod h1:3FbJWZp3XT9OWVN3Hmfp0p/a08v4h8J9W1aghka0soA= 17 | github.com/tdewolff/test v1.0.11-0.20231101010635-f1265d231d52 h1:gAQliwn+zJrkjAHVcBEYW/RFvd2St4yYimisvozAYlA= 18 | github.com/tdewolff/test v1.0.11-0.20231101010635-f1265d231d52/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE= 19 | github.com/yosssi/gohtml v0.0.0-20201013000340-ee4748c638f4 h1:0sw0nJM544SpsihWx1bkXdYLQDlzRflMgFJQ4Yih9ts= 20 | github.com/yosssi/gohtml v0.0.0-20201013000340-ee4748c638f4/go.mod h1:+ccdNT0xMY1dtc5XBxumbYfOUhmduiGudqaDgD2rVRE= 21 | golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= 22 | golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= 23 | golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= 24 | golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= 25 | golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM= 26 | golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 27 | golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= 28 | golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= 29 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 30 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 31 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 32 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 33 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 34 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 35 | -------------------------------------------------------------------------------- /image/img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamHuFei/KillWxapkg-Auto/30a26be50ce6bd3e92cad883c45312a9a63beef8/image/img.png -------------------------------------------------------------------------------- /image/img_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamHuFei/KillWxapkg-Auto/30a26be50ce6bd3e92cad883c45312a9a63beef8/image/img_1.png -------------------------------------------------------------------------------- /internal/cmd/cmd.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "log" 7 | "os" 8 | "path/filepath" 9 | "strings" 10 | 11 | "github.com/Ackites/KillWxapkg/internal/restore" 12 | "github.com/Ackites/KillWxapkg/internal/util" 13 | 14 | . "github.com/Ackites/KillWxapkg/internal/config" 15 | "github.com/Ackites/KillWxapkg/internal/decrypt" 16 | "github.com/Ackites/KillWxapkg/internal/unpack" 17 | ) 18 | 19 | // ParseInput 解析输入文件 20 | func ParseInput(input, fileExt string) []string { 21 | var inputFiles []string 22 | if fileInfo, err := os.Stat(input); err == nil && fileInfo.IsDir() { 23 | files, err := os.ReadDir(input) 24 | if err != nil { 25 | log.Fatalf("读取输入目录失败: %v", err) 26 | } 27 | for _, file := range files { 28 | if !file.IsDir() && strings.HasSuffix(file.Name(), fileExt) { 29 | inputFiles = append(inputFiles, filepath.Join(input, file.Name())) 30 | } 31 | } 32 | } else { 33 | inputFiles = strings.Split(input, ",") 34 | } 35 | 36 | // 过滤掉不存在的文件 37 | var validFiles []string 38 | for _, file := range inputFiles { 39 | if _, err := os.Stat(file); err == nil { 40 | validFiles = append(validFiles, file) 41 | } 42 | } 43 | return validFiles 44 | } 45 | 46 | // DetermineOutputDir 确定输出目录 47 | func DetermineOutputDir(input, appID string) string { 48 | var baseDir string 49 | 50 | if fileInfo, err := os.Stat(input); err == nil && fileInfo.IsDir() { 51 | baseDir = input 52 | } else { 53 | baseDir = filepath.Dir(input) 54 | } 55 | 56 | if appID == "" { 57 | return filepath.Join(baseDir, "result") 58 | } 59 | 60 | return filepath.Join(baseDir, appID) 61 | } 62 | 63 | // ProcessFile 合并目录 64 | func ProcessFile(inputFile, outputDir, appID string, save bool) error { 65 | log.Printf("开始处理文件: %s\n", inputFile) 66 | 67 | manager := GetWxapkgManager() 68 | 69 | // 初始化 WxapkgInfo 70 | info := &WxapkgInfo{ 71 | WxAppId: appID, 72 | IsExtracted: false, 73 | } 74 | 75 | // 确定解密后的文件路径 76 | decryptedFilePath := filepath.Join(outputDir, filepath.Base(inputFile)) 77 | 78 | // 解密 79 | decryptedData, err := decrypt.DecryptWxapkg(inputFile, appID) 80 | if err != nil { 81 | return fmt.Errorf("解密失败: %v", err) 82 | } 83 | 84 | // 保存解密后的文件 85 | err = os.MkdirAll(outputDir, 0755) 86 | if err != nil { 87 | return fmt.Errorf("创建输出目录失败: %v", err) 88 | } 89 | 90 | // 是否保存解密后的文件 91 | if save { 92 | err = os.WriteFile(decryptedFilePath, decryptedData, 0755) 93 | if err != nil { 94 | return fmt.Errorf("保存解密文件失败: %v", err) 95 | } 96 | 97 | log.Printf("文件解密并保存到: %s\n", decryptedFilePath) 98 | } 99 | 100 | // 解包到临时目录 101 | tempDir, err := os.MkdirTemp("", "wxapkg") 102 | if err != nil { 103 | return fmt.Errorf("创建临时目录失败: %v", err) 104 | } 105 | defer func(path string) { 106 | err := os.RemoveAll(path) 107 | if err != nil { 108 | log.Printf("删除临时目录 %s 失败: %v\n", path, err) 109 | } 110 | }(tempDir) 111 | 112 | // 包文件列表 113 | var filelist []string 114 | 115 | filelist, err = unpack.UnpackWxapkg(decryptedData, tempDir) 116 | if err != nil { 117 | return fmt.Errorf("解包失败: %v", err) 118 | } 119 | 120 | // 设置解包状态 121 | info.IsExtracted = true 122 | 123 | // 合并解包后的内容到输出目录 124 | err = mergeDirs(tempDir, outputDir) 125 | if err != nil { 126 | return fmt.Errorf("合并目录失败: %v", err) 127 | } 128 | 129 | info.WxapkgType = util.GetWxapkgType(filelist) 130 | 131 | if restore.IsMainPackage(info) { 132 | info.SourcePath = outputDir 133 | } else if restore.IsSubpackage(info) { 134 | info.SourcePath = filelist[0] 135 | } 136 | 137 | // 将包信息添加到管理器中 138 | manager.AddPackage(info.SourcePath, info) 139 | 140 | return nil 141 | } 142 | 143 | // mergeDirs 合并目录 144 | func mergeDirs(srcDir, dstDir string) error { 145 | return filepath.Walk(srcDir, func(path string, info os.FileInfo, err error) error { 146 | if err != nil { 147 | return err 148 | } 149 | relPath, err := filepath.Rel(srcDir, path) 150 | if err != nil { 151 | return err 152 | } 153 | targetPath := filepath.Join(dstDir, relPath) 154 | 155 | if info.IsDir() { 156 | return os.MkdirAll(targetPath, 0755) 157 | } 158 | 159 | srcFile, err := os.Open(path) 160 | if err != nil { 161 | return err 162 | } 163 | defer func(srcFile *os.File) { 164 | err := srcFile.Close() 165 | if err != nil { 166 | log.Printf("关闭文件 %s 失败: %v\n", srcFile.Name(), err) 167 | } 168 | }(srcFile) 169 | 170 | dstFile, err := os.Create(targetPath) 171 | if err != nil { 172 | return err 173 | } 174 | defer func(dstFile *os.File) { 175 | err := dstFile.Close() 176 | if err != nil { 177 | log.Printf("关闭文件 %s 失败: %v\n", dstFile.Name(), err) 178 | } 179 | }(dstFile) 180 | 181 | _, err = io.Copy(dstFile, srcFile) 182 | return err 183 | }) 184 | } 185 | -------------------------------------------------------------------------------- /internal/config/delete.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "context" 5 | "log" 6 | "os" 7 | "sync" 8 | ) 9 | 10 | // FileDeletionManager 用于管理需要删除的文件列表 11 | type FileDeletionManager struct { 12 | mu sync.Mutex 13 | files map[string]bool 14 | cancelFn context.CancelFunc 15 | ctx context.Context 16 | } 17 | 18 | var deleteInstance *FileDeletionManager 19 | var deleteOnce sync.Once 20 | 21 | // NewFileDeletionManager 创建或获取一个单例的FileDeletionManager 22 | func NewFileDeletionManager() *FileDeletionManager { 23 | deleteOnce.Do(func() { 24 | c := context.Background() 25 | ctx, cancel := context.WithCancel(c) 26 | deleteInstance = &FileDeletionManager{ 27 | files: make(map[string]bool), 28 | cancelFn: cancel, 29 | ctx: ctx, 30 | } 31 | }) 32 | return deleteInstance 33 | } 34 | 35 | // AddFile 添加文件路径到删除列表 36 | func (f *FileDeletionManager) AddFile(filePath string) { 37 | f.mu.Lock() 38 | defer f.mu.Unlock() 39 | f.files[filePath] = true 40 | } 41 | 42 | // DeleteFiles 删除所有在列表中的文件 43 | func (f *FileDeletionManager) DeleteFiles() { 44 | f.mu.Lock() 45 | files := make([]string, 0, len(f.files)) 46 | for file := range f.files { 47 | files = append(files, file) 48 | } 49 | f.mu.Unlock() 50 | 51 | for _, file := range files { 52 | select { 53 | case <-f.ctx.Done(): 54 | log.Println("文件删除操作已取消") 55 | return 56 | default: 57 | // 判断文件是否存在 58 | if _, err := os.Stat(file); os.IsNotExist(err) { 59 | continue 60 | } 61 | err := os.Remove(file) 62 | if err != nil { 63 | log.Printf("删除文件 %s 失败: %v\n", file, err) 64 | } else { 65 | f.mu.Lock() 66 | delete(f.files, file) // 删除成功后从列表中移除文件 67 | f.mu.Unlock() 68 | log.Printf("文件 %s 已成功删除", file) 69 | } 70 | } 71 | } 72 | } 73 | 74 | // Cancel 取消删除操作 75 | func (f *FileDeletionManager) Cancel() { 76 | f.cancelFn() 77 | } 78 | -------------------------------------------------------------------------------- /internal/config/share.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "sync" 5 | ) 6 | 7 | // SharedConfigManager 用于管理共享配置 8 | type SharedConfigManager struct { 9 | mu sync.RWMutex 10 | settings map[string]interface{} 11 | } 12 | 13 | var shareInstance *SharedConfigManager 14 | var shareOnce sync.Once 15 | 16 | // NewSharedConfigManager 创建一个新的SharedConfigManager 17 | func NewSharedConfigManager() *SharedConfigManager { 18 | shareOnce.Do(func() { 19 | shareInstance = &SharedConfigManager{ 20 | settings: make(map[string]interface{}), 21 | } 22 | }) 23 | return shareInstance 24 | } 25 | 26 | // Set 设置一个配置项的值 27 | func (scm *SharedConfigManager) Set(key string, value interface{}) { 28 | scm.mu.Lock() 29 | defer scm.mu.Unlock() 30 | scm.settings[key] = value 31 | } 32 | 33 | // SetBulk 批量设置配置项的值 34 | func (scm *SharedConfigManager) SetBulk(configs map[string]interface{}) { 35 | scm.mu.Lock() 36 | defer scm.mu.Unlock() 37 | for key, value := range configs { 38 | scm.settings[key] = value 39 | } 40 | } 41 | 42 | // Get 获取一个配置项的值 43 | func (scm *SharedConfigManager) Get(key string) (interface{}, bool) { 44 | scm.mu.RLock() 45 | defer scm.mu.RUnlock() 46 | value, exists := scm.settings[key] 47 | return value, exists 48 | } 49 | 50 | // GetBulk 批量获取配置项的值 51 | func (scm *SharedConfigManager) GetBulk(keys []string) map[string]interface{} { 52 | scm.mu.RLock() 53 | defer scm.mu.RUnlock() 54 | results := make(map[string]interface{}) 55 | for _, key := range keys { 56 | if value, exists := scm.settings[key]; exists { 57 | results[key] = value 58 | } 59 | } 60 | return results 61 | } 62 | 63 | // Delete 删除一个配置项 64 | func (scm *SharedConfigManager) Delete(key string) { 65 | scm.mu.Lock() 66 | defer scm.mu.Unlock() 67 | delete(scm.settings, key) 68 | } 69 | 70 | // GetAll 返回所有配置项的副本 71 | func (scm *SharedConfigManager) GetAll() map[string]interface{} { 72 | scm.mu.RLock() 73 | defer scm.mu.RUnlock() 74 | c := make(map[string]interface{}) 75 | for key, value := range scm.settings { 76 | c[key] = value 77 | } 78 | return c 79 | } 80 | -------------------------------------------------------------------------------- /internal/config/wxapkg.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "sync" 5 | 6 | "github.com/Ackites/KillWxapkg/internal/enum" 7 | ) 8 | 9 | // Parser 接口 10 | type Parser interface { 11 | Parse(option WxapkgInfo) error 12 | } 13 | 14 | // WxapkgOption 微信小程序解包选项 15 | type WxapkgOption struct { 16 | ViewSource string 17 | AppConfigSource string 18 | ServiceSource string 19 | SetAppConfig bool 20 | } 21 | 22 | // WxapkgInfo 保存包的信息 23 | type WxapkgInfo struct { 24 | WxAppId string 25 | WxapkgType enum.WxapkgType 26 | SourcePath string 27 | IsExtracted bool 28 | Option *WxapkgOption 29 | Parsers []Parser // 添加解析器列表 30 | } 31 | 32 | // WxapkgManager 管理多个微信小程序包 33 | type WxapkgManager struct { 34 | Packages map[string]*WxapkgInfo 35 | } 36 | 37 | var managerInstance *WxapkgManager 38 | var wxapkgOnce sync.Once 39 | 40 | // GetWxapkgManager 获取单例的 WxapkgManager 实例 41 | func GetWxapkgManager() *WxapkgManager { 42 | wxapkgOnce.Do(func() { 43 | managerInstance = &WxapkgManager{ 44 | Packages: make(map[string]*WxapkgInfo), 45 | } 46 | }) 47 | return managerInstance 48 | } 49 | 50 | // AddPackage 添加包信息 51 | func (manager *WxapkgManager) AddPackage(id string, info *WxapkgInfo) { 52 | manager.Packages[id] = info 53 | } 54 | 55 | // GetPackage 获取包信息 56 | func (manager *WxapkgManager) GetPackage(id string) (*WxapkgInfo, bool) { 57 | info, exists := manager.Packages[id] 58 | return info, exists 59 | } 60 | -------------------------------------------------------------------------------- /internal/decrypt/decrypt.go: -------------------------------------------------------------------------------- 1 | package decrypt 2 | 3 | import ( 4 | "bytes" 5 | "crypto/aes" 6 | "crypto/cipher" 7 | "crypto/sha1" 8 | "encoding/binary" 9 | "fmt" 10 | "os" 11 | 12 | "golang.org/x/crypto/pbkdf2" 13 | ) 14 | 15 | const ( 16 | saltStr = "saltiest" 17 | ivStr = "the iv: 16 bytes" 18 | fileHeader = "V1MMWX" 19 | defaultXorKey = 0x66 20 | ) 21 | 22 | func DecryptWxapkg(inputFile, appID string) ([]byte, error) { 23 | ciphertext, err := os.ReadFile(inputFile) 24 | if err != nil { 25 | return nil, fmt.Errorf("读取文件失败: %v", err) 26 | } 27 | 28 | // 先检查是否已解密 29 | reader := bytes.NewReader(ciphertext) 30 | var firstMark byte 31 | binary.Read(reader, binary.BigEndian, &firstMark) 32 | var info1, indexInfoLength, bodyInfoLength uint32 33 | binary.Read(reader, binary.BigEndian, &info1) 34 | binary.Read(reader, binary.BigEndian, &indexInfoLength) 35 | binary.Read(reader, binary.BigEndian, &bodyInfoLength) 36 | var lastMark byte 37 | binary.Read(reader, binary.BigEndian, &lastMark) 38 | if firstMark == 0xBE && lastMark == 0xED { 39 | // 已解密直接返回 40 | return ciphertext, nil 41 | } 42 | 43 | if string(ciphertext[:len(fileHeader)]) != fileHeader { 44 | return nil, fmt.Errorf("无效的文件格式") 45 | } 46 | 47 | key := pbkdf2.Key([]byte(appID), []byte(saltStr), 1000, 32, sha1.New) 48 | 49 | block, err := aes.NewCipher(key) 50 | if err != nil { 51 | return nil, fmt.Errorf("创建AES密码块失败: %v", err) 52 | } 53 | 54 | iv := []byte(ivStr) 55 | mode := cipher.NewCBCDecrypter(block, iv) 56 | originData := make([]byte, 1024) 57 | mode.CryptBlocks(originData, ciphertext[6:1024+6]) 58 | 59 | afData := make([]byte, len(ciphertext)-1024-6) 60 | var xorKey byte 61 | if len(appID) >= 2 { 62 | xorKey = appID[len(appID)-2] 63 | } else { 64 | xorKey = defaultXorKey 65 | } 66 | for i, b := range ciphertext[1024+6:] { 67 | afData[i] = b ^ xorKey 68 | } 69 | 70 | originData = append(originData[:1023], afData...) 71 | 72 | return originData, nil 73 | } 74 | -------------------------------------------------------------------------------- /internal/enum/wxapkg.go: -------------------------------------------------------------------------------- 1 | package enum 2 | 3 | // 定义微信小程序包的文件名常量 4 | const ( 5 | App_Config = "app-config.json" // 应用配置文件 6 | App_Service = "app-service.js" // 应用服务文件 7 | PageFrameHtml = "page-frame.html" // 页面框架HTML文件 8 | AppWxss = "app-wxss.js" // 应用样式文件 9 | CommonApp = "common.app.js" // 通用应用文件 10 | AppJson = "app.json" // 应用JSON文件 11 | Workers = "workers.js" // 工作者脚本文件 12 | Page_Frame = "page-frame.js" // 页面框架JS文件 13 | AppService = "appservice.js" // 应用服务文件 14 | PageFrame = "pageframe.js" // 页面框架JS文件 15 | Game = "game.js" // 游戏脚本文件 16 | GameJson = "game.json" // 游戏JSON文件 17 | SubContext = "subContext.js" // 子上下文脚本文件 18 | Plugin = "plugin.js" // 插件脚本文件 19 | PluginJson = "plugin.json" // 插件JSON文件 20 | ) 21 | 22 | // WxapkgType 定义微信小程序包的类型 23 | type WxapkgType string 24 | 25 | // 定义微信小程序包的类型常量 26 | const ( 27 | App_V1 WxapkgType = "APP_V1" // 应用类型 V1 28 | App_V2 WxapkgType = "APP_V2" // 应用类型 V2 29 | App_V3 WxapkgType = "APP_V3" // 应用类型 V3 30 | App_V4 WxapkgType = "APP_V4" // 应用类型 V4 31 | 32 | APP_SUBPACKAGE_V1 WxapkgType = "APP_SUBPACKAGE_V1" // 应用子包类型 V1 33 | APP_SUBPACKAGE_V2 WxapkgType = "APP_SUBPACKAGE_V2" // 应用子包类型 V2 34 | 35 | APP_PLUGIN_V1 WxapkgType = "APP_PLUGIN_V1" // 应用插件类型 V1 36 | 37 | GAME WxapkgType = "GAME" // 游戏类型 38 | GAME_SUBPACKAGE WxapkgType = "GAME_SUBPACKAGE" // 游戏子包类型 39 | GAME_PLUGIN WxapkgType = "GAME_PLUGIN" // 游戏插件类型 40 | 41 | FRAMEWORK WxapkgType = "FRAMEWORK" // 框架类型 42 | ) 43 | -------------------------------------------------------------------------------- /internal/formatter/formatter.go: -------------------------------------------------------------------------------- 1 | package formatter 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | . "github.com/Ackites/KillWxapkg/internal/config" 8 | ) 9 | 10 | // Formatter 是一个文件格式化器接口 11 | type Formatter interface { 12 | Format([]byte) ([]byte, error) 13 | } 14 | 15 | // 注册所有格式化器 16 | var formatters = map[string]Formatter{} 17 | 18 | // RegisterFormatter 注册文件扩展名对应的格式化器 19 | func RegisterFormatter(ext string, formatter Formatter) { 20 | formatters[strings.ToLower(ext)] = formatter 21 | } 22 | 23 | // GetFormatter 返回文件扩展名对应的格式化器 24 | func GetFormatter(ext string) (Formatter, error) { 25 | formatter, exists := formatters[strings.ToLower(ext)] 26 | if !exists { 27 | return nil, fmt.Errorf("不支持的文件类型: %s", ext) 28 | } 29 | configManager := NewSharedConfigManager() 30 | if pretty, ok := configManager.Get("pretty"); ok { 31 | if p, o := pretty.(bool); o { 32 | if !p && ext == ".js" { 33 | return nil, fmt.Errorf("不进行美化输出") 34 | } 35 | } 36 | } 37 | return formatter, nil 38 | } 39 | -------------------------------------------------------------------------------- /internal/formatter/htmlformatter.go: -------------------------------------------------------------------------------- 1 | package formatter 2 | 3 | import ( 4 | "bytes" 5 | "regexp" 6 | "strings" 7 | 8 | "github.com/yosssi/gohtml" 9 | ) 10 | 11 | // HTMLFormatter 结构体,用于格式化 HTML 代码 12 | type HTMLFormatter struct { 13 | jsFormatter *JSFormatter 14 | } 15 | 16 | // 正则表达式用于匹配 HTML 中的 `) 18 | 19 | // NewHTMLFormatter 创建一个新的 HTMLFormatter 实例 20 | func NewHTMLFormatter() *HTMLFormatter { 21 | return &HTMLFormatter{ 22 | jsFormatter: NewJSFormatter(), 23 | } 24 | } 25 | 26 | // Format 方法用于格式化 HTML 代码 27 | // input: 原始的 HTML 代码字节切片 28 | // 返回值: 格式化后的 HTML 代码字节切片和错误信息(如果有) 29 | func (f *HTMLFormatter) Format(input []byte) ([]byte, error) { 30 | // 使用 gohtml 库格式化 HTML 代码 31 | data := gohtml.FormatBytes(bytes.TrimSpace(input)) 32 | 33 | // 替换