Skip to content

Commit 4c516e3

Browse files
committed
feat: 新增重新打包wxapkg文件功能
1 parent 5c09c84 commit 4c516e3

File tree

9 files changed

+298
-17
lines changed

9 files changed

+298
-17
lines changed

README.md

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@
3535
- [x] Wxml代码还原
3636
- [x] Wxss代码还原
3737
- [x] Hook小程序,动态调试,开启小程序F12
38+
- [x] 重新打包小程序,可破解小程序
39+
- [x] 监视将要打包的文件夹,并自动打包
3840
- [ ] 支持小游戏
3941
- [ ] 敏感数据导出
4042

@@ -87,6 +89,24 @@
8789

8890
<img src="./images/img9.jpg" width="60%">
8991

92+
### 重新打包运行,可破解小程序
93+
94+
```shell
95+
<本程序> -repack=<输入目录> [-out=<输出目录或文件>] [-watch]
96+
```
97+
98+
<img src="./images/img10.png" width="30%">
99+
100+
#### 效果示例
101+
102+
**修改前**
103+
104+
<img src="./images/img11.png" width="30%">
105+
106+
**修改后**
107+
108+
<img src="./images/img12.png" width="30%">
109+
90110
## 安装
91111

92112
- 下载最新版本的[release](https://github.com/Ackites/KillWxapkg/releases)
@@ -108,7 +128,7 @@
108128
## 用法
109129

110130
> -id=<输入AppID> -in=<输入文件1,输入文件2> 或 -in=<输入目录> -out=<输出目录>
111-
> [-ext=<文件后缀>] [-restore] [-pretty] [-noClean] [-help] [-hook] [-save]
131+
> [-ext=<文件后缀>] [-restore] [-pretty] [-noClean] [-help] [-hook] [-save] [-repack=<输入目录>] [-watch]
112132
113133
### 参数说明
114134
- `-id string`
@@ -134,9 +154,15 @@
134154
- 是否清理反编译的中间文件,默认清理
135155
- `-hook`
136156
- 是否Hook小程序,动态调试,开启F12,默认不Hook
137-
- 注意:目前仅支持Windows
157+
- **注意:目前仅支持Windows**
138158
- `-save`
139159
- 是否保存解密后的文件,默认不保存
160+
- `-repack string`
161+
- 重新打包目录路径
162+
- 例:-repack="C:\Users\mi\Desktop\Applet\64"
163+
- **注意:目前仅支持一次打包一个文件,同时仅支持未被解析的源文件(未使用-restore)**
164+
- `-watch`
165+
- 是否监听将要打包的文件夹,并自动打包,默认不监视
140166
- `-help`
141167
- 显示帮助信息
142168

go.mod

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,17 @@ go 1.22
55
require (
66
github.com/ditashi/jsbeautifier-go v0.0.0-20141206144643-2520a8026a9c
77
github.com/dop251/goja v0.0.0-20240731150404-c665f0b58f6e
8+
github.com/fsnotify/fsnotify v1.7.0
89
github.com/tdewolff/parse/v2 v2.7.15
910
github.com/yosssi/gohtml v0.0.0-20201013000340-ee4748c638f4
1011
golang.org/x/crypto v0.25.0
1112
golang.org/x/net v0.27.0
13+
golang.org/x/text v0.16.0
1214
)
1315

1416
require (
1517
github.com/dlclark/regexp2 v1.7.0 // indirect
1618
github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect
1719
github.com/google/pprof v0.0.0-20230207041349-798e818bf904 // indirect
18-
golang.org/x/text v0.16.0 // indirect
20+
golang.org/x/sys v0.22.0 // indirect
1921
)

go.sum

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ github.com/dlclark/regexp2 v1.7.0 h1:7lJfhqlPssTb1WQx4yvTHN0uElPEv52sbaECrAQxjAo
44
github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
55
github.com/dop251/goja v0.0.0-20240731150404-c665f0b58f6e h1:jhfevGje1Gw4uDNv30Kj+tl/SpHb4La9InZOnwMrNUs=
66
github.com/dop251/goja v0.0.0-20240731150404-c665f0b58f6e/go.mod h1:o31y53rb/qiIAONF7w3FHJZRqqP3fzHUr1HqanthByw=
7+
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
8+
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
79
github.com/go-sourcemap/sourcemap v2.1.3+incompatible h1:W1iEw64niKVGogNgBN3ePyLFfuisuzeidWPMPWmECqU=
810
github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg=
911
github.com/google/pprof v0.0.0-20230207041349-798e818bf904 h1:4/hN5RUoecvl+RmJRE2YxKWtnnQls6rQjjW5oV7qg2U=
@@ -18,6 +20,8 @@ golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
1820
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
1921
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
2022
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
23+
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
24+
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
2125
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
2226
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
2327
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=

images/img10.png

30 KB
Loading

images/img11.png

447 KB
Loading

images/img12.png

406 KB
Loading

internal/pack/pack.go

Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
package pack
2+
3+
import (
4+
"encoding/binary"
5+
"fmt"
6+
"io"
7+
"log"
8+
"os"
9+
"path/filepath"
10+
"strings"
11+
12+
"github.com/fsnotify/fsnotify"
13+
)
14+
15+
func Repack(path string, watch bool, outputDir string) {
16+
// 过滤空白字符
17+
path = strings.TrimSpace(path)
18+
outputDir = strings.TrimSpace(outputDir)
19+
20+
// 如果是目录,则打包目录
21+
if fileInfo, err := os.Stat(path); err != nil || !fileInfo.IsDir() {
22+
log.Printf("错误: %s 不是一个有效的目录\n", path)
23+
return
24+
}
25+
26+
// 打包目录
27+
err := packWxapkg(path, outputDir)
28+
if err != nil {
29+
log.Printf("错误: %v\n", err)
30+
return
31+
}
32+
33+
if watch {
34+
watchDir(path, outputDir)
35+
}
36+
37+
return
38+
}
39+
40+
type WxapkgFile struct {
41+
NameLen uint32
42+
Name string
43+
Offset uint32
44+
Size uint32
45+
}
46+
47+
// 打包文件到 wxapkg 格式
48+
func packWxapkg(inputDir string, outputDir string) error {
49+
var files []WxapkgFile
50+
var totalSize uint32
51+
52+
// 检查 outputDir 是否存在及其类型
53+
outputInfo, err := os.Stat(outputDir)
54+
outputFile := ""
55+
if err != nil {
56+
if os.IsNotExist(err) {
57+
// 如果 outputDir 不存在,假设它是一个文件路径或一个需要创建的目录
58+
if filepath.Ext(outputDir) == "" {
59+
// 创建目录
60+
if err := os.MkdirAll(outputDir, 0755); err != nil {
61+
return fmt.Errorf("无法创建输出目录: %w", err)
62+
}
63+
outputFile = filepath.Join(outputDir, "output.wxapkg")
64+
} else {
65+
outputFile = outputDir
66+
}
67+
} else {
68+
return fmt.Errorf("无法访问输出目录: %w", err)
69+
}
70+
} else {
71+
if outputInfo.IsDir() {
72+
outputFile = filepath.Join(outputDir, "output.wxapkg")
73+
} else {
74+
outputFile = outputDir
75+
}
76+
}
77+
78+
// 计算文件列表
79+
err = filepath.Walk(inputDir, func(path string, info os.FileInfo, err error) error {
80+
if err != nil {
81+
return err
82+
}
83+
84+
// 排除目录
85+
if info.IsDir() {
86+
return nil
87+
}
88+
89+
// 排除 .wxapkg 文件
90+
if filepath.Ext(path) == ".wxapkg" {
91+
return nil
92+
}
93+
94+
relPath, _ := filepath.Rel(inputDir, path)
95+
relPath = "/" + filepath.ToSlash(relPath) // 确保路径以 '/' 开头,并且使用 Unix 风格的路径分隔符
96+
size := uint32(info.Size())
97+
98+
files = append(files, WxapkgFile{
99+
NameLen: uint32(len(relPath)),
100+
Name: relPath,
101+
Offset: totalSize, // 预计算文件偏移量
102+
Size: size,
103+
})
104+
105+
totalSize += size
106+
return nil
107+
})
108+
if err != nil {
109+
return fmt.Errorf("计算文件列表失败: %w", err)
110+
}
111+
112+
// 创建输出文件
113+
outFile, err := os.Create(outputFile)
114+
if err != nil {
115+
return fmt.Errorf("创建输出文件失败: %w", err)
116+
}
117+
defer func(outFile *os.File) {
118+
err := outFile.Close()
119+
if err != nil {
120+
log.Printf("关闭输出文件失败: %v\n", err)
121+
}
122+
}(outFile)
123+
124+
// 写入文件头
125+
if err := binary.Write(outFile, binary.BigEndian, byte(0xBE)); err != nil {
126+
return fmt.Errorf("写入文件头标记失败: %w", err)
127+
}
128+
129+
info1 := uint32(0) // 示例值
130+
if err := binary.Write(outFile, binary.BigEndian, info1); err != nil {
131+
return fmt.Errorf("写入 info1 失败: %w", err)
132+
}
133+
134+
// 计算索引段长度,包含每个文件的元数据长度和文件名长度
135+
var indexInfoLength uint32
136+
for _, file := range files {
137+
indexInfoLength += 4 + uint32(len(file.Name)) + 4 + 4 // NameLen + Name + Offset + Size
138+
}
139+
140+
if err := binary.Write(outFile, binary.BigEndian, indexInfoLength); err != nil {
141+
return fmt.Errorf("写入索引段长度失败: %w", err)
142+
}
143+
144+
bodyInfoLength := totalSize
145+
if err := binary.Write(outFile, binary.BigEndian, bodyInfoLength); err != nil {
146+
return fmt.Errorf("写入数据段长度失败: %w", err)
147+
}
148+
149+
if err := binary.Write(outFile, binary.BigEndian, byte(0xED)); err != nil {
150+
return fmt.Errorf("写入文件尾标记失败: %w", err)
151+
}
152+
153+
// 写入文件数量
154+
fileCount := uint32(len(files))
155+
if err := binary.Write(outFile, binary.BigEndian, fileCount); err != nil {
156+
return fmt.Errorf("写入文件数量失败: %w", err)
157+
}
158+
159+
// 写入索引段
160+
for _, file := range files {
161+
if err := binary.Write(outFile, binary.BigEndian, file.NameLen); err != nil {
162+
return fmt.Errorf("写入文件名长度失败: %w", err)
163+
}
164+
if _, err := outFile.Write([]byte(file.Name)); err != nil {
165+
return fmt.Errorf("写入文件名失败: %w", err)
166+
}
167+
// 加上 18 字节文件头长度和索引段长度
168+
if err := binary.Write(outFile, binary.BigEndian, file.Offset+indexInfoLength+18); err != nil {
169+
return fmt.Errorf("写入文件偏移量失败: %w", err)
170+
}
171+
if err := binary.Write(outFile, binary.BigEndian, file.Size); err != nil {
172+
return fmt.Errorf("写入文件大小失败: %w", err)
173+
}
174+
}
175+
176+
// 写入数据段
177+
for _, file := range files {
178+
func() {
179+
f, err := os.Open(filepath.Join(inputDir, file.Name[1:])) // 去掉路径开头的 '/' 以正确打开文件
180+
if err != nil {
181+
log.Printf("打开文件失败: %v\n", err)
182+
return
183+
}
184+
defer func(f *os.File) {
185+
err := f.Close()
186+
if err != nil {
187+
log.Printf("关闭文件失败: %v\n", err)
188+
}
189+
}(f)
190+
191+
if _, err = io.Copy(outFile, f); err != nil {
192+
log.Printf("写入文件内容失败: %v\n", err)
193+
}
194+
}()
195+
}
196+
197+
return nil
198+
}
199+
200+
func watchDir(inputDir string, outputDir string) {
201+
watcher, err := fsnotify.NewWatcher()
202+
if err != nil {
203+
log.Println("ERROR: ", err)
204+
return
205+
}
206+
defer func(watcher *fsnotify.Watcher) {
207+
err := watcher.Close()
208+
if err != nil {
209+
log.Println("ERROR: ", err)
210+
}
211+
}(watcher)
212+
213+
done := make(chan bool)
214+
go func() {
215+
for {
216+
select {
217+
case event := <-watcher.Events:
218+
if event.Op&fsnotify.Write == fsnotify.Write || event.Op&fsnotify.Create == fsnotify.Create || event.Op&fsnotify.Remove == fsnotify.Remove {
219+
log.Println("检测到文件变化: ", event.Name)
220+
if err := packWxapkg(inputDir, outputDir); err != nil {
221+
log.Println("打包失败: ", err)
222+
} else {
223+
log.Println("打包成功")
224+
}
225+
}
226+
case err := <-watcher.Errors:
227+
log.Println("ERROR: ", err)
228+
}
229+
}
230+
}()
231+
232+
err = watcher.Add(inputDir)
233+
if err != nil {
234+
log.Println("ERROR: ", err)
235+
}
236+
<-done
237+
}

internal/unpack/unpack.go

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -154,18 +154,18 @@ func UnpackWxapkg(data []byte, outputDir string) ([]string, error) {
154154
return nil, <-errChan
155155
}
156156

157-
const configJSON = `{
158-
"description": "See https://developers.weixin.qq.com/miniprogram/dev/devtools/projectconfig.html",
159-
"setting": {
160-
"urlCheck": false
161-
}
162-
}`
163-
164-
// 保存 project.config.json
165-
configFile := filepath.Join(outputDir, "project.private.config.json")
166-
if err := os.WriteFile(configFile, []byte(configJSON), 0755); err != nil {
167-
return nil, fmt.Errorf("保存文件 %s 失败: %w", configFile, err)
168-
}
157+
//const configJSON = `{
158+
// "description": "See https://developers.weixin.qq.com/miniprogram/dev/devtools/projectconfig.html",
159+
// "setting": {
160+
// "urlCheck": false
161+
// }
162+
// }`
163+
164+
// 保存 project.private.config.json
165+
//configFile := filepath.Join(outputDir, "project.private.config.json")
166+
//if err := os.WriteFile(configFile, []byte(configJSON), 0755); err != nil {
167+
// return nil, fmt.Errorf("保存文件 %s 失败: %w", configFile, err)
168+
//}
169169

170170
return filelistNames, nil
171171
}

0 commit comments

Comments
 (0)