├── .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 | 
27 |
28 | 4. 【计划】改成WEBUI界面,方便使用。(问为啥不改成客户端,就是说个人感觉打开界面太多了会太杂乱,不如一个浏览器一个窗口)
29 |
30 | ## 介绍
31 | >主要新增KillWxapkg-Auto.exe -auto用法, 其余用法参照原作者。
32 |
33 | config/config.yaml 配置小程序目录绝对路径
34 |
35 | > KillWxapkg-Auto.exe -auto
36 |
37 | 
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 | // 替换